Ember.js session cookie based authentication with Rails and devise - ruby-on-rails

I'm looking to satisfy 3 goals with my Ember.js app authentication using rails, devise and a cookie based session.
Redirected to #/sessions/new if they're not logged in.
Always show the current user's information in the application template.
If the user is logged in and they go to #/some/route directly. The current user should be loaded on load.
I've watched these embercast videos: Client-side Authentication Part 1 & Client-side Authentication Part 2. They're a little out of date but helpful.
But still can't full solution. Anyone have full Rails 4, Devise, Emberjs 1.0.0 example?
Biggest problem is having a strategy to load the current user on page load and setting the current user when the sign in form is submitted.
Right now this is my strategy:
App.User = Em.Object.extend();
App.User.reopenClass({
current: function() {
return Ember.$.getJSON("/users/current").then(function(data) {
return data
})
}
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return App.User.current();
}
});
App.SessionsNewController = Ember.ObjectController.extend({
actions: {
save: function(data) {
var self = this, data = this.getProperties('email', 'password');
$.post("/sessions", { session: data }).always(function(response, status, data) {
if (status == "success") {
self.transitionToRoute('index');
} else {
self.set('errorMessage', data);
}
})
},
}
});

I would not say this is not doable. But you will do lots of extra and unnecessary works to get the authentication working, which can all be done with a simple page redirect.
I've collected some opinions from Derick, the author of Backbone.Marionette. Though these are for Backbone but not Ember.js, the situation of client side authentication is same.
I find it painful and unnecessary to try and make Backbone/Marionette handle the authentication and re-loading of the authorized site stuff. Once they log in, redirect them to a different URL that the server handles, and have the server send down all the stuff that they need, as an authenticated user. https://stackoverflow.com/a/18151935
Another quote from Derick as well:
Right. And there’s a lot of cases where I just flat out say, “Do not do single-page applications,” as well. And a login screen is the biggest example of that. In all of the clients that I’ve had in the last couple of years, they’ve all asked me, “Hey, I’m having this problem. I’m trying to get my login screen to give me the current user information back from the server and redo all of this stuff on the screen without refreshing everything.” My answer every single time is, “Don’t do that." http://javascriptjabber.com/056-jsj-marionette-js-with-derick-bailey/
Also think about other cases, say Gmail. You won't get a smooth transition after click "Sign in" button on Gmail's sign in page. There will be redirect with rather big data loading as well :)
From users' perspective, they won't say Gmail is not great just because there is a redirect after signing in. After all signing/sign up is much much less frequent than daily mail operations.
So my suggestion is, reload all resources after user session changed. Let Rails and Devise do these dirty jobs in traditional fashion.

Related

Creating a User Time Sheet for a RoR Web App

I'm kind of a newb to RoR and I'm working on creating my first web app.
So...My question is, how do I create a user time sheet in RoR?
What I need to do is create a classroom time sheet for students' (Users) reading times at home.
The students (Users) are able to sign up and have a profile created. From there, I would like for them to have access to log in their reading time(s).
I have attached examples of just some simple timesheets that would work perfectly for this.
I just do not know where to start and have not been able to find any gems that could help me create this.
Time Sheet 1
TimeSheet 2
Users: Ruby part
Use Devise gem - it will save a lot of time for you.
Add Devise to user model (something like that: rails generate devise User), then autogenerate basic Devise pages (sign in, sign up, etc), see Devise tutorials:
https://launchschool.com/blog/how-to-use-devise-in-rails-for-authentication
http://guides.railsgirls.com/devise
Also you'll probably need something like Job model with fields user_id, time_spent, date or something.
Timesheets: JS part
Time tracking is more front-end part of work, so you'll need to write some JS scripts for time tracking, which will monitor user activity and then send it to Job mobel on Rails side.
Track time spent on page (example):
var page_opened;
$(document).ready(function () {
page_opened = Date.getTime();
$(window).unload(function () {
page_closed = Date.getTime();
$.ajax({
url: "/save_user_time",
data: {
'timeSpent': page_closed - page_opened,
'job_id': job_id
}
})
});
}
Also you defenetly should take a look on some basic Rails tutorials for better understanding:
http://guides.rubyonrails.org/getting_started.html
https://www.codecademy.com/learn/learn-rails

Rails Session Timeout/Warning

I've been trying to research this all day and haven't really been able to find anything that fits my needs. We need to create a session timeout (simple) for our application, but we also need a warning message to display x minutes before the timeout of the session will occur.
I have my app set up with the activerecord_session gem and have done the necessary configuration changes. I CAN write up something like this:
before_action :determine_if_session_timed_out
in the ApplicationController. My main concern with doing it this way is db hits and the fact that it seems like there should be a rails way to do it, similar to how the above linked gem has expire_after: xx minutes, but for a warning.
My two questions are--has anyone seen any apps that:
have a built-in rails way of displaying a warning when the session is about to time out?
automatically redirect after the session times out? It looks like the page doesn't redirect with the expire_after implementation for the above gem. It stays on the same page until another action is called and then redirects.
Ajax requests in your application can be caught by this piece of code,.
$(document).ajaxError(function(e, error) { switch(error.status) {
case 401: {
// page redirect unauthorized access.
location.reload();
alert(xhr.responseText);
} }
You can have look at this approach with jquery (https://codedecoder.wordpress.com/2014/05/01/jquery-inactivity-warning-logout-timer-setinterval-clearinterval/)

How can I test Stripe.js using poltergeist and Capybara?

I've been going nuts trying to write an automated test for my user sign up page. Users will be charged a recurring subscription via Stripe. They input their basic details (email, password, etc) and their credit card details on the same form, then the following flow happens:
(On the client-side) stripe.js makes an AJAX request to Stripe's servers, which (assuming everything is valid) returns a credit card token.
My javascript fills in a hidden input in the HTML form with the credit card token, and submits the form to my Rails server.
(Now on the server-side): I validate the user's basic details. If they're invalid, return (because there's no point charging them via Stripe if e.g. their email address is invalid so they can't create an account anyway.)
If they're valid, attempt to create a Stripe::Customer object, add the right subscription and charge them using Stripe's ruby gem etc.
All of this works perfectly fine... except I can't figure out how to test it. Testing step #4 is easy enough as it takes place on the server-side so I can mock out the Stripe calls with a gem like VCR.
Step #1 is what's giving me trouble. I've tried to test this using both puffing-billy and the stripe-ruby-mock gem, but nothing works. Here's my own javascript (simplified):
var stripeResponseHandler = function (status, response) {
console.log("response handler called");
if (response.error) {
// show the errors on the form
} else {
// insert the token into the form so it gets submitted to the server
$("#credit_card_token").val(response.id);
// Now submit the form.
$form.get(0).submit();
}
}
$form.submit(function (event) {
// Disable the submit button to prevent repeated clicks
$submitBtn.prop("disabled", true);
event.preventDefault();
console.log("creating token...");
Stripe.createToken(
// Get the credit card details from the form
// and input them here.
}, stripeResponseHandler);
// Prevent the form from submitting the normal way.
return false;
});
Just to reiterate, this all works fine when I test it manually. But my automated tests fail:
Failure/Error: expect{submit_form}.to change{User.count}.by(1)
expected result to have changed by 1, but was changed by 0
When I try to use the gem puffing-billy, it seems to be caching stripe.js itself (which is loaded from Stripe's own servers at js.stripe.com, not served from my own app, as Stripe don't support this.), but the call initiated by Stripe.createToken isn't being cached. In fact, when I log into my Stripe server logs, it doesn't seem that the call is even been made (or at least Stripe isn't receiving it.)
Note those console.log statements in my JS above. When I run my test suite, the line "creating token..." gets printed, but "response handler called." doesn't. Looks like the response handler is never being called.
I've left out some details because this question is already very long, but can add more on request. What am I doing wrong here? How can I test my sign up page?
UPDATE See [my comment on this Github issue] on stripe-ruby-mock for more info on what I've tried and failed.
If I understand correctly...
Capybara won't know about your ajax requests. You should be able to stub out AJAX requests with Sinatra. Have it return a fixtures much the same as VCR.
Here's an article on it.
https://robots.thoughtbot.com/using-capybara-to-test-javascript-that-makes-http
You need to boot the Sinatra app in Capybara and then match the URLs in your ajax calls.
Something like:
class FakeContinousIntegration < Sinatra::Base
def self.boot
instance = new
Capybara::Server.new(instance).tap { |server| server.boot }
end
get '/some/ajax'
# send ajax back to capybara
end
end
When you boot the server, it will return the address and port which you can write to a config that your js can use.
#server = App.boot
Then I use the address and port to config the JS app
def write_js_config
config['api'] = "http://#{#server.host}:#{#server.port}"
config.to_json
end
In spec_helper.rb send in the config to the js so your script points to your sinatra app. Mine compiles with gulp. So I just build the config into to is before the tests run:
system('gulp build --env capybara')
I've had tests which worked on manual fail in Capybara/poltergeist due to timeout. In my case, the solution was to wait for all AJAX requests to finish. Reference
Not sure whether Stripe.js uses JQuery internally, try checking for a condition set by stripeResponseHandler.
In addition to the wait_for_ajax trick mentioned, it looks like you are calling expect before your database was updated. One way to check that would be to add a breakpoint in your code(binding.pry), and check if it is a race condition issue or not.
Also, as per Capybara's documentation, introducing an expectation of a UI change makes it 'smartly' wait for ajax calls to finish:
expect(page).not_to have_content('Enter credit card details')

How to securely register non-routing clicks on page via AJAX/update action?

I want to register various clicks on a webpage, e.g. toggle visibility of elements.
The clicks come from users not logged in.
I have an Impression model with an actions attribute that stores key-value pairs of actions made on a certain page.
On a click event I'm updating a record with this function:
function sendAjax(id, data) {
$.ajax({
type: "PATCH",
url: '/impressions/update',
data: {'impression_id' : id, 'actions' : data},
success: function(events){
}
});
}
But I'm realizing that this is not secure at all, the user could theoretically update whatever record she wants.
How could I do this more securely, can I take advantage of Rails' protect_from_forgery in any way with my use case?
I don't think this is a necessary feature. Maybe you think too much :)
Even in Google Analytic you can't stop a visitor from manipulating his action, theoretically. One can push any events he want just in console.
Also it's not necessary for analytic tool to be 100% secure and precise. There must be noises, you can ease them but can't really avoid them, or avoid them in a reasonable cost.
You just need to find the Impression from impressions belongs to the current_user.
Example:
def update
#impression = current_user.impressions.find(params[:impression_id])
# ...
end

Handling user registration in an Ember.js/Rails/Devise app

I'm playing around with writing a pure Ember.js app on top of Rails 4 and I'm puzzled how user management is handled. My original idea was to use pure server-rendered templates (ERB) to do the user registration and login via Devise, and then the rest of the app would use the Ember framework.
The problem with that is that Ember wants to take over the <body> tag and control the entire viewport. In this way I can't pick and choose which aspects of the app should use server-rendered Erb templates and which should live in the Ember logic.
I see plenty of examples of how to deal with a user that's already logged-in and ember-auth looks interesting to facilitate authentication-aware controllers, but I've seen no tutorials or suggestions on allowing the full user signup experience to take place in the Ember app.
Am I missing something, either from a technical perspective where I just haven't found the right code or from a architectural perspective where I shouldn't be doing it this way?
This is with ember-rails (0.12.0 w/1.0.0.rc3.3 ember-source), Rails 4.0.0.rc1, and Devise (rails4 branch).
ember-auth dev here.
You don't actually need any special treatment for user sign up. Treat user sign up as you would for another model, in the sense that creating a user model will not require authentication. (Editing it or deleting it should require authentication though.)
Your implementation might look like:
App.User = DS.Model.extend
email: DS.attr 'string'
password: DS.attr 'string'
App.UsersNewRoute = Em.Route.extend
model: ->
App.User.createRecord()
App.UsersNewController = Em.ObjectController.extend
create: ->
#store.commit()
Error-checking, template code, etc, skipped for brevity.
This is here for reference to what worked based off of #heartsentwined's answer since pasting in comments doesn't work very well. See the comments for more info. Since my api returns the user json I just pass in the format its expecting.
didCreate: function() {
var user = App.Auth.get('_response').response.user;
var auth = {auth_token: user.auth_token, id: user.id};
App.Auth.get('_response').canonicalize(auth);
App.Auth.trigger('signInSuccess');
}
UPDATE:
I switched to ember-model and now do this in the same place that I call model.save() (the submit action of SignupController).
var model = this.get('model');
model.on('didCreateRecord', function() {
var user = this.data;
var auth = {auth_token: user.auth_token, user_id: user.id, remember_token: user.remember_token};
App.Auth.get('_response').canonicalize(auth);
App.Auth.trigger('signInSuccess');
});
model.save();
The solutions above ALMOST but not quite worked for me. Here is what did work:
didCreate: function() {
var user = App.Auth.get('_response').response.user;
App.Auth.signIn({
data: {
'email': user.email,
'password': this.get('password'),
'remember': true
}
});
}
App.Auth.signIn is used in the documentation explicitly: http://ember-auth.herokuapp.com/docs

Resources