User Session Authentication in Rails 5 - ruby

I am trying to implement notification using the action cable in Rails 5.
After reading the tutorial for the Action cable, which work on the session & Cookies based Authentication to receive the notification for the current logged in user.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.username
end
protected
def find_verified_user
if verified_user = env['warden'].session(:user)
verified_user
else
reject_unauthorized_connection
end
end
end
In the find_verified_user , I am not able to get the user object from the session.
Can anyone help me, to authenticate the user in action cable.

If your session_store (config/initializers/session_store.rb) uses :cookie_store, then you can read the session from encrypted cookies:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
if user = User.find_by(id: session[:user_id])
self.current_user = user
else
reject_unauthorized_connection
end
end
private
def session
key = Rails.application.config.session_options.fetch(:key)
cookies.encrypted[key]&.symbolize_keys || {}
end
end
end

Because you have not access to session, you must use cookie:
1) Go to your_app/config/initializers/session_store.rb and paste this code:
Rails.application.config.session_store :cookie_store, key: 'your_super_secret_code'
2) After anywhere you can get user id:
key = Rails.application.config.session_options.fetch(:key)
cookies.encrypted[key]&.symbolize_keys || {}
User.find(cookies["warden.user.user.key"][0][0])
Example for session: How can I find a devise user by it's session id?

Related

Google API Server-to-Server Communication not working (Ruby implementation)

I have followed all the steps to set up a Domain-Wide Delegation of Authority (https://developers.google.com/admin-sdk/reports/v1/guides/delegation) and followed this link too (https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority) and I ended up creating this class in Ruby.
class Gsuite::ServiceAccount
def initialize(person: nil)
end
def authorized_client
Google::Auth::ServiceAccountCredentials.make_creds(
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:gsuite_service_account]
[:credentials_file]),
scope: AppConfig[:gsuite_service_account][:scope])
client = authorizer.fetch_access_token!
end
end
This class returns me this hash
{"access_token"=>"a_long_token_string_here", "expires_in"=>3600, "token_type"=>"Bearer"}
Then, I've created this method (within my Admin class) to connect to Gmail Service
def gsuite_client_access
#client ||= Gsuite::ServiceAccount.new(person: self.email.to_s).authorized_client
authorized_client = Google::Apis::GmailV1::GmailService.new
authorized_client.authorization = #client
authorized_client
end
So, when I try to list my Gmail Messages with this line in another part of the code
inbox = current_admin.gsuite_client_access.list_user_messages('me', max_results: 10)
I get the following error message =>
Sending HTTP get https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10
401
#<Hurley::Response GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10 == 401 (238 bytes) 645ms>
Caught error Unauthorized
Error - #<Google::Apis::AuthorizationError: Unauthorized>
Retrying after authentication failure
Google::Apis::AuthorizationError: Unauthorized
Any ideas what's missing here?
Finally, I got it working. Turns out, you need to use this line to use the sub method to the "impersonated user" to be able to connect.
authorizer.sub = #person
And, for your delight, here is the updated test code for reading Gmail messages so you can follow in case you want to use it. Just remember to save the credentials.json file in your project folder to make it work and use the same scope you added in the GSuite Dashboard.
class Gsuite::ServiceAccount
def initialize(person: nil)
#person = person
end
def read_messages
client = service_account_access
inbox = client.list_user_messages(#person, max_results: 5, label_ids: "INBOX" )
if inbox.result_size_estimate.nonzero?
inbox.messages.each do |message|
response = client.get_user_message(#person, message.id)
end
end
end
private
def service_account_access
token = authorized_client
client = Signet::OAuth2::Client.new(access_token: token['access_token'])
client.expires_in = Time.current + token["expires_in"]
auth_client = Google::Apis::GmailV1::GmailService.new
auth_client.authorization = client
auth_client
end
def authorized_client
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:credentials_file]),
scope: AppConfig[:gsuite_scope]).dup
authorizer.sub = #person
authorizer.fetch_access_token!
end
end

Mailer unable to access reset_token in User model

faced with an issue where #user.reset_token returns nil.
app/views/user_mailer/password_reset.html.erb
<%= link_to "Reset password", edit_password_reset_url(#user.reset_token, email: #user.email) %>
Reset_token is declared in User model, whereby this problem happens when I try to use a sidekiq worker. Refer to code below.
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :reset_token
def User.new_token
SecureRandom.urlsafe_base64
end
def send_password_reset_email
PasswordResetWorker.perform_async(self.id)
end
private
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
app/workers/password_reset_worker.rb
class PasswordResetWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform(user_id)
user = User.find(user_id)
UserMailer.password_reset(user).deliver
end
end
app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default from: "noreply#example.com"
def password_reset(user)
#user = user
mail to: user.email, subject: "Password Reset"
end
end
This problem DOES NOT happen when I do not use workers
app/models/user.rb
def send_password_reset_email
UserMailer.password_reset(self).deliver
end
Would like to know what can I replace "#user.reset_token" with? Let me know if you need more info. Thanks in advance.
You're not storing the reset_token in the database - you're storing the reset_digest.
When you don't use workers, you're storing the reset_token in the User instance, then passing that same User instance to your mailer - hence the reset_token is still available.
When you use workers, your worker only has the User's ID, so it's reloading the User instance from the database. Because the reset_token isn't being stored in the database, it's coming back nil.
Either you should be saving the reset_token in the database, or your password email should be using reset_digest in the URL.

Devise not storing sessions and losing credentials after redirect

It is a VERY strange bug and I am leading with it for 24 hours. It was working well and suddenly it started to fail.
The problem:
When I want to login with Facebook, the app redirec to Facebook permissions request, go back, save the update in the account model (access_token, and updated_at), but I am redirected to the home without permissions to access to signed_in sections.
My stack is:
Rails4, Devise 3.0.0.rc, Omniauth, Omniauth-facebook 1.4.0.
The app only accept login with Facebook.
Take a look:
Omniauth controller: account_signed_in? = true
class Accounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#account = Account.find_for_facebook_oauth(request.env["omniauth.auth"], current_account)
if #account.persisted?
sign_in_and_redirect #account, :event => :authentication #this will throw if #user is not activated
puts account_signed_in? # <-- true
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_account_registration_url
end
end
ApplicationController: account_signed_in? = true
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def stored_location_for(resource_or_scope)
nil
end
def after_sign_in_path_for(resource_or_scope)
puts account_signed_in? # <-- true
current_account.pages.empty? ? new_page_path : pages_path
end
StaticController (home) account_signed_in? = false
class StaticController < ApplicationController
def home
puts account_signed_in? # <- false
render layout: 'home'
end
I don't know if can there be something that disturb the normal flow of sessions between Devise and Rails.
Found that!
The sessions weren't saved because of the domain parameter in session_store.rb:
BrainedPage::Application.config.session_store :cookie_store,
key: '_my_session', :domain => Rails.configuration.domain
Seems I had changed the domain configuration in development environment (added port, because I was using this var for other propose too), and I didn't realize the impact it could make.

Uninitialized Constant Error from Ruby EventMachine Chat Server

I'm trying to build a chat server in ruby using EventManager. Needless to day, I'm new to Ruby and feeling a little over my head with the current error I am getting, as I have no clue what it means and a search doesn't return anything valuable. Here's some of the logistics-
(ive only implemented LOGIN and REGISTER so I'll only include those..)
user can enter-
REGISTER username password - registers user
LOGIN username password - logins user
I'm taking in the string of data the user sends, splitting it into an array called msg, and then acting on the data based on msg[0] (as its the command, like REGISTER, LOGIN, etc)
Here is my code, all contained in a single file- chatserver.rb (explanation follows):
require 'rubygems'
require 'eventmachine'
class Server
attr_accessor :clients, :channels, :userCreds, :userChannels
def initialize
#clients = [] #list of clients connected e.g. [192.168.1.2, 192.168.1.3]
#users = {} #list of users 'logged in' e.g. [tom, sam, jerry]
#channels = [] #list of channels e.g. [a, b, c]
#userCreds = {} #user credentials hash e.g. { tom: password1, sam: password2, etc }
#userChanels = {} #users and their channels e.g. { tom: a, sam: a, jerry: b }
end
def start
#signature = EventMachine.start_server("127.0.0.1", 3200, Client) do |con|
con.server = self
end
end
def stop
EventMachine.stop_server(#signature)
unless wait_for_connections_and_stop
EventMachine.add_periodic.timer(1) { wait_for_connections_and_stop }
end
end
# Does the username already exist?
def has_username?(name)
#userCreds.has_key?(name)
end
# Is the user already logged in?
def logged_in?(name)
if #users[name] == 1
true
else
false
end
end
# Did the user enter the correct pwd?
def correct_pass?(pass)
if #userCreds[name] == pass
true
else
false
end
end
private
def wait_for_connections_and_stop
if #clients.empty?
EventMachine.stop
true
else
puts "Waiting for #{#clients.size} client(s) to stop"
false
end
end
end
class Connection < EventMachine::Connection
attr_accessor :server, :name, :msg
def initialize
#name = nil
#msg = []
end
# First thing the user sees when they connect to the server.
def post_init
send_data("Welcome to the lobby.\nRegister or Login with REGISTER/LOGIN username password\nOr try HELP if you get stuck!")
end
# Start parsing incoming data
def receive_data(data)
data.strip!
msg = data.split("") #split data by spaces and throw it in array msg[]
if data.empty? #the user entered nothing?
send_data("You didn't type anything! Try HELP.")
return
elsif msg[0] == "REGISTER"
handle_register(msg) #send msg to handle_register method
else
hanlde_login(msg) #send msg to handle_login method
end
end
def unbind
#server.clients.each { |client| client.send_data("#{#name} has just left") }
puts("#{#name} has just left")
#server.clients.delete(self)
end
private
def handle_register(msg)
if #server.has_username? msg[1] #user trying to register with a name that already exists?
send_data("That username is already taken! Choose another or login.")
return
else
#name = msg[1] #set name to username
#userCreds[name] = msg[2] #add username and password to user credentials hash
send_data("OK") #send user OK message
end
end
end
EventMachine::run do
s = Server.new
s.start #start server
puts "Server listening"
end
Whew, okay, it's only the beginning, so not that complicated. Since I'm new to Ruby I have a feeling I'm just not declaring variable or using scope correctly. Here's the error output:
chatserver.rb:16:in start': uninitialized constant Server::Client
(NameError) from chatserver.rb:110:inblock in ' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
call' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
run_machine' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
run' from chatserver.rb:108:in<\main>'
ignore the slash in main in that last line.
line 108 is the last function- EventMachine::run do etc.
Any help would be appreciated, if I didn't provide enough info just let me know.
I would think that when you call EventMachine::start_server you need to give it your Connection class as the handler. Client is not defined anywhere.

Trying to log in with a specific client from my app

So I`m using the google-api-ruby-client to make a google analytics app, and I wanted to log in every time with a specific user instead of having to be redirected to oauth everytime.
My question is: is there any way to insert the login/password of that client into the code so when I use the app I don't have to authorize anything?
Here is the code that makes the autentication:
class TokenPair
attr_accessor :id
attr_accessor :refresh_token
attr_accessor :access_token
attr_accessor :issued_at
def initialize
##id ||= 1
self.id = ##id
##id += 1
end
def self.get(id)
##els ||= {}
tp = ##els.fetch(id, TokenPair.new)
##els[tp.id] = tp
end
def update_token!(object)
self.refresh_token = object.refresh_token
self.access_token = object.access_token
#self.expires_in = object.expires_in
self.issued_at = object.issued_at
end
def to_hash
{
refresh_token: refresh_token,
access_token: access_token,
# expires_in: expires_in,
issued_at: issued_at ? Time.at(issued_at) : ''
}
end
end
def logout
reset_session
redirect_to root_url
end
def logged_in?
if session["token_id"]
redirect_to profile_path
end
end
def login
logged_in?
end
def self.params
##params
end
def update_token
#client = Google::APIClient.new
#client.authorization.client_id = '209273986197.apps.googleusercontent.com'
#client.authorization.client_secret = '6sCG5ynCiz9Ej07pwIm653TU'
#client.authorization.scope = 'https://www.googleapis.com/auth/analytics.readonly'
#client.authorization.redirect_uri = callback_url
#client.authorization.code = params[:code] if params[:code]
logger.debug session.inspect
if session[:token_id]
# Load the access token here if it's available
token_pair = TokenPair.get(session[:token_id])
#client.authorization.update_token!(token_pair.to_hash)
end
if #client.authorization.refresh_token && #client.authorization.expired?
#client.authorization.fetch_access_token!
end
#analytics = #client.discovered_api('analytics', 'v3')
unless #client.authorization.access_token || request.path_info =~ /^\/oauth2/
redirect_to authorize_path
end
end
def authorize
redirect_to #client.authorization.authorization_uri.to_s, :status => 303
end
def callback
begin
#client.authorization.fetch_access_token!
# Persist the token here
token_pair = TokenPair.get(session[:token_id])
token_pair.update_token!(#client.authorization)
session[:token_id] = token_pair.id
redirect_to profile_url
rescue ArgumentError
redirect_to root_url
end
end
def get_web_properties
result = #client.execute(
api_method: #analytics.management.profiles.list,
parameters: {accountId: "~all", webPropertyId: "~all"}
#parameters: {accountId: "582717"}
)
result.data
end
Even if your app is always acting as the same user, OAuth is still the preferred mechanism for various reasons -- easier to revoke access, limited access in case of compromise, client login auth mechanism is deprecated, etc.
By default the client will request offline access, which allows you to keep refreshing the access token indefinitely without having to go through the full oauth flow each time. You can simply authorize the app once, save the refresh token, and when it expires, just call fetch_access_token! again. If you're using the latest version of the library, the client will automatically attempt refreshing the token if it expired, so all you need to take care of is the initial authorization & storage of the refresh token.

Resources