Create a restricted area for authenticated user only - ember.js

I've made a login system with Ember.js and Rails . So I tried to make redirection for non-authenticated users. I don't know where I must do the redirection (the application controller or in the application route ? ).
Here's my auth system :
window.AuthApp = Ember.Object.extend(Ember.Evented).create
authToken: null
currentUserId: null
signIn: (data) ->
if data == null
data = {}
if data['remember'] == true
cookie = true
$.ajax 'api/login',
type: 'POST'
dataType: 'JSON',
contentType: 'application/json; charset=utf-8'
data: JSON.stringify(data)
error: ->
alert('Error signIn')
success: (data) ->
console.log(data)
AuthApp.set 'authToken', data['auth_token']
if cookie == true
$.cookie 'remember_token', data['auth_token'],
expires: 7
AuthApp.Module.RememberMe = Ember.Object.create
recall: ->
if ($.cookie('remember_token'))
data = {}
data['auth_token'] = $.cookie('remember_token')
AuthApp.signIn(data)
return true
As you can see, I've just to call AuthApp.Module.Remember.recall() for check if an user is connected or not.
Thanks for your help

You should take a look at the router-facelift. One of the design goals was to make authentication-based apps less difficult to implement.
https://gist.github.com/machty/5723945

Related

React put method not updating the user from Django

I have a Django as backend and updating the user from postman is working fine. But when I update it via React Frontend, it replies with a success message just as in Postman, but the data was not updated.
This is the update function to update:
const updateData = (e) => {
e.preventDefault();
const csrftoken = getCookie("csrf");
const cookies = new Cookies();
const url = "http://localhost:8000/usercontrol/update";
setIsLoading(true);
fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Token " + cookies.get("token"),
"X-CSRFToken": csrftoken,
},
body: JSON.stringify({
email: userinfo.email,
username: userinfo.username,
first_name: userinfo.first_name,
last_name: userinfo.last_name,
}),
}).then((response) => console.log("THE RESPONSE: ", response.json()));
setIsLoading(false);
};
This is what it prints out in the console
Since I am partially following CodingWithMitch for Django user creation with rest framework is similar to his.
Furthermore, since there is no error outputting and is working fine in Postman, I have no idea what is wrong with it.

Single test fails in PhantomJS but works in Chrome and Firefox

I have a single acceptance test in Ember.js 1.10, Ember CLI 0.1.12 which fails on PhantomJS but runs fine in both Chrome and Firefox. I've tried to debug this for 2 days but I'm running out of ideas. Name of the test is user can view logged-only pages when he is logged. Basically when you are not logged and you try to access classic route, for example /about you are redirected to start.login in beforeModel hook of classic route:
beforeModel: ->
if not #controllerFor('application').get 'logged'
#transitionTo 'start.login'
When you are on start.login and you will give correct name and username, logIn action in StartLoginController will be called:
logIn: ->
if not #get('logged')
#get('controllers.application').send 'logIn', #get('username'), #get('password'), #get('rememberMe')
Which calls following action in ApplicationController:
actions:
logIn: (username, password, remember) ->
if not #get 'logged'
$.ajax
url: #get('apiURL') + '/auth/login'
type: 'POST'
data:
name: username
password: password
remember: remember
xhrFields:
withCredentials: true #ensure CORS
.then (response) =>
if response.success
expires = if remember then new Date(response.cookieExpiration) else null
$.cookie 'auth_user_id', response.user.id,
expires: expires
path: '/'
$.cookie 'auth_expiration', response.cookieExpiration,
expires: expires
path: '/'
#setProperties
logged: true
'controllers.auth.userId': response.user.id
#transitionToRoute 'classic.index'
, =>
#send 'openModal', 'modal/wrong-credentials'
false
And this works fine even in PhantomJS. Other tests pass. Actions are called correctly, properties are set correctly, cookies are set correctly. Even beforeModel hook correctly calls(or not) transitionTo method. I've thought that issue with PhantomJS is with some async order of calling things but I've tried wrapping code in Ember.run and andThen in many places. No luck at all.
testem.json:
{
"framework": "qunit",
"test_page": "tests/index.html?hidepassed",
"launch_in_ci": [
"PhantomJS"
],
"launch_in_dev": [
"PhantomJS",
"Chrome",
"Firefox"
]
}
Acceptance test login-test.coffee(failing test is the last one):
`import Ember from 'ember'`
`import startApp from '../helpers/start-app'`
application = null
run = Ember.run
login = ->
visit '/'
fillIn '[placeholder=Login]', 'test'
fillIn '[placeholder=Hasło]', 'test'
click '.button-login'
clearCookies = ->
cookies = $.cookie()
for cookie of cookies
$.removeCookie cookie,
path: '/'
cookies = document.cookie.split ';'
for i in [0...cookies.length] by 1
equals = cookies[i].indexOf '='
name = if equals > -1 then cookies[i].substr(0, equals) else cookies[i]
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"
module 'Acceptance: Login',
setup: ->
application = startApp()
clearCookies()
time = new Date()
$.mockjaxSettings.logging = false
$.mockjax
type: 'POST'
url: 'api/v1/auth/login'
dataType: 'json'
responseText:
success: true
user:
id: 1
cookieExpiration: time.setDate time.getDate() + 14
$.mockjax
type: 'GET'
url: '/api/v1/users/1'
dataType: 'json'
responseText:
user:
id: 1
$.mockjax
type: 'GET'
url: '/api/v1/statuses' # ?limitOld=10&friends=true&comments[limit]=5
data:
comments:
limit: 5
friends: true
limitOld: 10
responseText:
statuses: {}
$.mockjax
type: 'GET'
url: '/api/v1/getRandomQuote'
$.mockjax
type: 'GET'
url: '/api/v1/statuses/?friends=true&count=true'
responseText:
count: 0
$.mockjax
type: 'GET'
url: '/api/v1/meals'
$.mockjax
type: 'GET'
url: '/api/v1/notifications'
$.mockjax
type: 'GET'
url: '/api/v1/notifications'
data:
read: false
responseText: {}
return
teardown: ->
$.mockjax.clear()
clearCookies()
run application, 'destroy'
test 'user lands on default page when he is not logged', ->
expect 1
visit '/'
andThen ->
equal currentPath(), 'start.login'
test 'login page is displayed when you are trying to access logged-only page', ->
expect 1
visit '/kitchen'
andThen ->
equal currentPath(), 'start.login'
test 'user can login', ->
expect 2
appController = application.__container__.lookup 'controller:application'
equal appController.get('logged'), false, 'user is not logged before login'
login()
andThen ->
ok appController.get 'logged', 'user is logged when response is success'
test 'user can view logged-only pages when he is logged', ->
expect 2
console.log '-- LOGGED-ONLY TEST START --'
visit '/about'
andThen ->
equal currentPath(), 'start.login'
login()
visit '/about'
andThen ->
equal currentPath(), 'classic.about'
And finally output from tests:
TEST'EM 'SCRIPTS!
Open the URL below in a browser to connect.
http://localhost:7357/
━━━━━━━━━━━━━━┓
PhantomJS 1.9┃ Firefox 37.0
198/199 ✘ ┃ 199/199 ✔
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Acceptance: Login: user can view logged-only pages when he is logged
✘ failed
expected classic.about
actual start.login
-- LOGGED-ONLY TEST START --
I don't think classic.about is source of error, because replacing it with other child routes of classic resource result in same PhantomJS failing test.
Okay so it seems that problem lies in model for ClassicRoute(commenting that makes test pass):
model: ->
new Promise (resolve, reject) =>
#store.find 'user', #controllerFor('auth').get('userId')
.then (user) =>
#controllerFor('auth').set 'user', user
resolve
profile: user.get 'profile'
I've split logic in two hooks instead of one and it works now:
beforeModel: ->
if not #controllerFor('application').get 'logged'
#transitionTo 'start.login'
else
#store
.find 'user', #controllerFor('auth').get('userId')
.then (user) =>
#controllerFor('auth').set 'user', user
#set 'profileId', user.get('profile.id')
model: ->
#store.find 'profile', #get('profileId')

Got CSRF verification failure when while requesting POST through API

I'm writing a site using REST API. I use django with piston at backend (also using corsheaders.middleware.CorsMiddleware with CORS_ORIGIN_ALLOW_ALL = True). And I use backbone.js for frontend. I'm sending POST request from client-side and get error:
CSRF verification failed. Request aborted.
I've googled a lot and all solutions suggested something like "Use the render shortcut which adds RequestContext automatically". But I have no view, forms will be requested from frontend, that shouldn't know about how backend works. Here's code of my scipt
Question = Backbone.Model.extend({
urlRoot: 'http://example.com/api/questions',
defaults: {
id: null,
title: '',
text: ''
},
initialize: function() {
//alert(this.title);
}
});
var question2 = new Question;
var questionDetails = {title: 'test title', text: 'test text'};
question2.save(questionDetails, {
success: function(question) {
alert(question.toJSON());
}
});
The django docs have instructions on how to set up jquery to send the csrf token via ajax.
https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
You should make sure that the template tag {% csrf_token %} renders something in your frontend. That way you know that the token is being created and passed to the frontend. If you follow the instructions from the docs above then your csrf token should always be sent with ajax requests. This is what the javascript looks like for one of my sites (assuming you are using jQuery).
// Set up Django CSRF Token Protection
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
crossDomain: false, // obviates need for sameOrigin test
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Also, make sure that 'django.middleware.csrf.CsrfViewMiddleware' is in your MIDDLEWARE_CLASSES settings.
Sounds like you need to pass the CSRF token through with your save request.
One solution would be to pass the CSRF token back to the model requesting it, then override your model's save method ensuring the model passes the CSRF token back with it.
Question = Backbone.Model.extend({
urlRoot: 'http://example.com/api/questions',
defaults: {
csrf: null,
id: null,
title: '',
text: ''
},
initialize: function() {
//alert(this.title);
}
save: function( data, options ){
data = $.extend( true, {
csrf: this.get( 'csrf' )
}, data );
options = _.extend( options, {
error: onError,
success: onSuccess
} );
// Call super method.
Backbone.Model.prototype.save.apply( this, [ data, options ] );
}
});

Basic HTTP Authentication in Ember Data REST Adapter

Is there a way in Ember.js (and Ember-data) to send credentials to an api that requires Basic HTTP Authentication? I can see how it's done in JQuery here, but don't see any straightforward way to do it in Ember. I thought maybe adding something to the header would work (see below in coffeescript), but no success:
App.AuthAdapter = DS.RESTAdapter.extend(
host: 'https://my-api.example.com'
namespace: 'v1'
headers:
"Authorization Basic fooUsername:barPassword"
...
You can extend the default Rest adapter and add a headers hash which will be included in the ajax that's sent.
App.ApplicationAdapter = DS.RESTAdapter.extend(
headers:
withCredentials: true
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
)
Or you could take it a step farther and override the ajax method
App.ApplicationAdapter = DS.RESTAdapter.extend(
ajax: (url, type, hash) ->
adapter = this
new Ember.RSVP.Promise((resolve, reject) ->
hash = hash or {}
hash.url = url
hash.type = type
hash.dataType = "json"
hash.context = adapter
if hash.data and type isnt "GET"
hash.contentType = "application/json; charset=utf-8"
hash.data = JSON.stringify(hash.data)
if adapter.headers isnt `undefined`
headers = adapter.headers
hash.beforeSend = (xhr) ->
forEach.call Ember.keys(headers), (key) ->
xhr.setRequestHeader key, headers[key]
hash.success = (json) ->
Ember.run null, resolve, json
hash.error = (jqXHR, textStatus, errorThrown) ->
Ember.run null, reject, adapter.ajaxError(jqXHR)
Ember.$.ajax hash
)
)
Can you use $.ajaxPrefilter? e.g.
Ember.$.ajaxPrefilter (options) ->
options.xhrFields = { withCredentials: true }
options.username = 'fooUsername'
options.password = 'barPassword'
true # need to return non-falsy here
As #gerry3 stated $.ajaxPrefilter is a valid solution.
But if you want to solve a problem of dynamically changing your Headers AFTER an event, for instance, a successful LOGIN attempt, then you need to put more wires. In my case I need to send back a 'Token' Header that is provided by the server after a successful AJAX-login. But, of course, when the user initiates the App he's not logged-in already.
The problem is that once you reopen or extend the RESTAdapter, or define an ajaxPrefilter, even if you're binding it to a value (or localStorage as in my case) the class won't be following the current variable value. It's like a snapshot taken at some moment. So it's useless in my scenario.
I'm following Embercast Client Authentication which is a good start (code available), but instead of jQuery data-fetching I'm using Ember-Data.
So the trick is to observe the token and re-define the ajaxPrefilter as many times as you need it.
tokenChanged: function() {
this.get('token')=='' ?
localStorage.removeItem('token') :
localStorage.token = this.get('token');
$.ajaxPrefilter(function(options, originalOptions, xhr) {
return xhr.setRequestHeader('Token', localStorage.token);
});
}.observes('token')
Therefore, when the user logs-in he'll have a valid token and send it in every request to the server via the RESTAdapter.
Hope this helps someone.

How can I modify an adapter after login?

Heres my setup:
Ember: 1.1.0-beta.1
Ember-data: 1.0.0-beta.2
# ON STARTUP
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: {
'X-API-TOKEN': localStorage.token
}
)
This works fine if they were already logged in when they refresh...
However...
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
APP.ApplicationAdapter = DS.RESTAdapter.reopen(
headers: {
'X-API-TOKEN': localStorage.token
}
)
else
#set('error_message', response.error)
The calls are still unauthorized, and the adapter keep trying to pass the old token.
So basically the 'headers' property on the adapter is not updated by the 'reopen' method. Whats the proper way to change the adapter in ember-data?
Thanks guys
Once instantiated you should get your adapter from the container.
But since it's not very clear from where you are making the request, try something like this:
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
adapter = APP.__container__.lookup 'adapter:Rest'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
else
#set('error_message', response.error)
Note: using App.__container__ is not recomended for production code depending from where you are making the request it would be more appropriate to get the container in a more clean way.
Update in response to your last comment
In the case you need access to the container in a controller then:
APP.LoginController = Ember.ObjectController.extend
...
adapter = #get('container').lookup 'adapter:application'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
...
should get you the adapter.
Hope it helps.
Headers can be used as a computed property to support dynamic headers.
You can use the volatile function to set the property into a non-cached mode causing the headers to be recomputed with every request.
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: function() {
return {
'X-API-TOKEN': localStorage.token
};
}.property().volatile()
)
URL's:
toc_headers-customization
method_volatile