Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails - A database connection per request, based on user credentials

We have the following problem: our app interfaces with exchange via its exchange web services (EWS), you can think of it as being just a fancy DB. As an example, we have an Appointment model which should be synchronized with the calendar on Exchange. Now EWS requires the users credentials for every connection. This creates 2 issues:

  • how do we get the users credential on every request?
  • how should the model interact with EWS?

What we don't want to do is ask the user for his password on every action that requires an interaction with EWS. We also don't want to sacrifice security.

Our current solution:

We store the credentials from the user (which we get during the login) in the session. This is ugly because it means the username and password are stored in a cookie on the clients computer - I know it is encrypted, but it's still ugly.

Then in the controller, we lazily create an EWS Client and pass it to each model instance which needs to interact with EWS. This is ugly because it means that unlike other DB operations (and you can think of EWS as just another database), it means you have to do additional steps for each operation.

Example (simplified/stripped down):

# app/models/appointments.rb
class Appointment
  attr_writer :ews

  def pull_from_exchange
    exchange_event = ews.events[exchange_id, exchange_change_key]
    # …update from exchange_event.to_hash…

    self
  end

  def push_to_exchange
    exchange_event = ews.events.new(to_hash_for_exchange)
    exchange_event.save
    update_attributes(exchange_id: exchange_event.id, exchange_change_key: exchange_event.change_key)

    self
  end

  def ews
    raise EwsClientNotProvided unless @ews
    @ews
  end

  def save_and_push!
    self.class.transaction do
      save!
      push_to_exchange
    end
  end
end

# app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
  def create
    @appointment = Appointment.new(params[:appointment])
    @appointment.ews = ews
    @appointment.save_and_push!
  end

private
  def ews
    @ews ||= Ews.client(session.slice(:username, :password, :email).merge(endpoint: ews_endpoint))
  end

  def ews_endpoint
    Rails.application.configuration.ews_endpoint
  end
end

I'm interested in alternative and/or better designs to deal with the situation.

Any ideas/suggestions?

like image 282
apeiros Avatar asked Nov 18 '25 23:11

apeiros


1 Answers

For the DB connection, I recommend Thread.current[] in a rackapp, pseudo-global variables are a necessary evil sometimes.

Storing the User credentials on the client is the optimal solution, you could spice the password field with yet another encryption to protect it against brute-forcing. This is because the cookie is hashed and therefore it might be possible to brute-force it, since it's possible to check wherever the decryption is successful locally. If you store the credentials somewhere on your server, all the passwords are compromised if the server is hacked. If you store the data on the client side, they are more secure, as you need both parts (the key on the server and the cookie).

like image 123
Reactormonk Avatar answered Nov 21 '25 14:11

Reactormonk