Ember auth transition.retry() after login doesn't transit while testing - ember.js

I am having some issues with testing my login and related features of my app. The app works perfectly, but the test fails. For testing, I use a Qunit with karma
I have created few authenticated routes(say accounts) which one can only visit after logging in. If a user goes to accounts route without logging, he is redirected to login page and after successful login, redirected back to the accounts page.
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!App.AuthManager.isAuthenticated()) {
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
var loginController;
loginController = this.controllerFor('login');
loginController.set("attemptedTransition", transition);
this.transitionTo("login");
},
events: {
error: function(reason, transition) {
this.redirectToLogin(transition);
}
}
});
App.LoginController = Ember.ObjectController.extend({
attemptedTransition: null,
loginUser: function() {
var attemptedTran, form_data, router, that;
router = this.get("target");
form_data = this.getProperties("email", "password");
attemptedTran = this.get('attemptedTransition');
that = this;
return $.post("/sessions", {
'session': form_data
}, (function(results) {
return Ember.run(function() {
App.AuthManager.authenticate(results.api_key.access_token, results.api_key.user_id);
if (attemptedTran) {
attemptedTran.retry();
return that.set('attemptedTransition', null);
} else {
return router.transitionTo("index");
}
});
}), "json");
}
});
App.AccountsRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.find('account');
}
});
I am trying to test this using
test("account index", function() {
expect(3); // Ensure that we will perform one assertion
visit("/accounts").andThen(function() {
equal(currentRouteName(),"login",'Accounts is an authenticated Route. so redirected to login page');
fillIn('input[type=text]', "j#j.com");
fillIn('input[type=password]', "jjjjjj");
click("button:submit").andThen(function(){
equal(currentRouteName(),"accounts",'After login redirected back to account page.');
})
});
But this test fails after logging in and doesn't redirect back to the accounts page.
Any help??

It looks like you're setting previousTransition then getting attemptedTransition. attemptedTransition should always be null according to the logic above.

Related

Trouble transitioning to current user after signing in with Emberfire

I am using Emberfire for my authentication and database and I am able to sign in, but it does not redirect me to my current users page after that. It also throws an error at me saying that no user exists at the custom url but my firebase console shows the user with the correct id that I am redirecting to.
Here is my login controller:
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
return this.get('session').fetch().catch(function() {});
},
actions: {
signIn: function() {
var controller = this.get('controller');
var email = controller.get('email');
var password = controller.get('password');
this.get('session').open('firebase', { provider: 'password', 'email': email, 'password': password}).then(function(data) {
console.log(data.currentUser);
});
let uid = this.get('session').get('uid');
this.store.findRecord('user', uid).then(user => {
console.log(user.get('firstName'));
this.transitionTo('user', uid);
});
},
signOut: function() {
this.get('session').close();
}
}
});
Here is my router:
Router.map(function() {
this.route('signup');
this.route('home', { path: '/' });
this.route('login');
this.route('user', { path: '/user/:user_id' });
});
You have to wait for the authentication to finish. That's what the .then is for. In your current code, you're getting the uid from the session before it's finished authenticating.
This should work:
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
return this.get('session').fetch().catch(function() {});
},
actions: {
signIn: function() {
var controller = this.get('controller');
var email = controller.get('email');
var password = controller.get('password');
var self = this;
this.get('session').open('firebase', { provider: 'password', 'email': email, 'password': password}).then(function(data) {
console.log(data.currentUser);
let uid = this.get('session').get('uid');
this.store.findRecord('user', uid).then(user => {
console.log(user.get('firstName'));
self.transitionTo('user', uid);
});
});
},
signOut: function() {
this.get('session').close();
}
}
});

Emberjs: How to redirect to the last accessed route after session invalidated

We are using ember-simple-auth with cookie authentication and we want to redirect to the last accessed route after we login again when the cookie expires. We manage to do the redirection for the following scenarios:
Not authenticated and try to access a route from url
Not authenticated and select an item from the navigation menu
Both, after successful authentication, we redirected to the requested route.
But, we want when our session cookie expired and the user tries to access a route to invalidate the session and redirect the user back to authentication page. When the user log in back we want to redirect him to the requested route. For now we store the previous transition so we can do the redirection but after we invalidate the session the data are lost.
What is the best way to do this?
Our code looks like:
Custom Authenticator
import Ember from 'ember';
import Base from 'ember-simple-auth/authenticators/base';
export default Base.extend({
restore() {
return new Ember.RSVP.Promise(function(resolve, reject) {
let sessionCookie = window.Cookies.get('beaker.session.id');
if(!window.isUndefined(sessionCookie)) {
resolve(true);
}else{
reject();
}
});
},
authenticate(data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
Ember.$.ajax({
type: 'post',
url: '/core/authentication/basic/login',
data: data
}).then((response) => {
resolve({
responseText: response
});
}, (error) => {
reject(error);
});
});
},
invalidate() {
return new Ember.RSVP.Promise(function (resolve, reject) {
Ember.$.ajax({
type: 'post',
url: '/core/authentication/basic/logout'
}).then(() => {
resolve(true);
}, () => {
reject();
});
});
}
});
Application Route:
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
session: Ember.inject.service('session'),
beforeModel(transition) {
if(!this.get('session.isAuthenticated') && transition.targetName !== 'core.authentication') {
this.set('previousTransition', transition);
this.transitionTo('core.authentication');
}
},
actions: {
willTransition(transition) {
if (!this.get('session.isAuthenticated')) {
this.set('previousTransition', transition);
} else {
let previousTransition = this.get('previousTransition');
if (previousTransition) {
this.set('previousTransition', null);
previousTransition.retry();
}
}
}
}
});
Authentication Route
import Ember from 'ember';
export default Ember.Route.extend({
session: Ember.inject.service('session'),
actions: {
login() {
let that = this;
let { username, password } = this.controller.getProperties('username', 'password');
let data = {username: username, password: password};
if(this.get('session.isAuthenticated')) {
this.get('session').invalidate();
}
this.get('session').authenticate('authenticator:basic', data).then(() => {
let data = that.get('session.data.authenticated');
// show response message
}, (error) => {
// show error
});
}
}
});
You can add the previous transition inside the session data, like this
this.get('session').set('data.previousTransition', transition.targetName);
because that is still persisted after the session is invalidated.
And then get it back from the store, and do the transition:
this.get('session.store').restore().then(data => {
if (data.previousTransition !== null) {
this.transitionTo(data.previousTransition)
}
})
I solved it by using invalidationSucceded here.
this.get('session').on('invalidationSucceeded', () => this.transitionToRoute('dashboard'))

ember-cli custom authenticator simple auth session authentication failed

What do I need to add to the code to initiate the sessionAuthenticationFailed(error). Right now it works when I have a successful login but I would like it also to show a message when when an incorrect username and/or password is entered.
here is what I have within authenticate in my custom authenticator
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.post( _this.serverTokenEndpoint, {
email: credentials.identification,
password: credentials.password
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.session.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
I would also like to show an error message. What do I need to put in my loginController.
The session's authenticate method returns a promise. You can attach a then to that and handle it accordingly in your controller, e.g.:
this.get('session').authenticate('authenticator', { … }).then(function() { /*success*/ }, function() { /* error */ });
or if you're using the LoginControllerMixin:
export Ember.Route.extend(LoginControllerMixin, {
actions: {
authenticate: function() {
this._super().then(function() { /*success*/ }, function() { /* error */ });
}
}
});
The sessionAuthenticationFailed should be called automatically anyway whenever authentication fails but if you want to e.g. display an error message when authentication fails etc. I'd use above approach.

How to store the user in a session

I am trying to set up ember-simple-auth with a django-rest-framework backend, but I'm running into some trouble saving the user to the session. I have to be able to do something like this in my templates:
<h2>Welcome back, {{session.user}}</h2>
So following several guides I found, I have got the authentication and authorization working so that I can get a valid token and use is in requests. To get the user on the session, I have modified App.CustomAuthenticator.authenticate so that when the token is returned, the username is also stored to the session:
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({username: credentials.identification, password: credentials.password }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({
token: response.token,
username: credentials.identification
});
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
I then modified Application.intializer to give session a user property:
Ember.Application.initializer({
name: 'authentication',
before: 'simple-auth',
initialize: function(container, application) {
// register the custom authenticator and authorizer so Ember Simple Auth can find them
container.register('authenticator:custom', App.CustomAuthenticator);
container.register('authorizer:custom', App.CustomAuthorizer);
SimpleAuth.Session.reopen({
user: function() {
var username = this.get('username');
if (!Ember.isEmpty(username)) {
return container.lookup('store:main').find('user', {username: username});
}
}.property('username')
});
}
});
However, when {{session.user.username}} is rendered it is just an empty string. My questions are:
Is this really the best way to assigning a user to the session? It seems clumsy to me but I can't see anything better.
I assume that the empty string is because a Promise is returned rather than a User object, so how to I resolve it?
To tag off of #marcoow's response, here's how to implement it in Ember CLI:
index.html:
window.ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:devise',
session: 'session:withCurrentUser'
};
initializers/customize-session.js:
import Session from 'simple-auth/session';
var SessionWithCurrentUser = Session.extend({
currentUser: function() {
var userId = this.get('user_id');
if (!Ember.isEmpty(userId)) {
return this.container.lookup('store:main').find('user', userId);
}
}.property('user_id')
});
export default {
name: 'customize-session',
initialize: function(container) {
container.register('session:withCurrentUser', SessionWithCurrentUser);
}
};
With the 0.6.4 release you can now specify a custom session class without having to reopen, see release note here: https://github.com/simplabs/ember-simple-auth/releases/tag/0.6.4. This is how it works:
App.CustomSession = SimpleAuth.Session.extend({
account: function() {
var accountId = this.get('account_id');
if (!Ember.isEmpty(accountId)) {
return this.container.lookup('store:main').find('account', accountId);
}
}.property('account_id')
});
…
container.register('session:custom', App.CustomSession);
…
window.ENV['simple-auth'] = {
session: 'session:custom',
}

Ember-simple-auth ApplicationRoute model function

I have classic setup of Ember-simple-auth, in ApplicationRoute I use
model: function () {
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function(data) {
return data.get('content')[0]
})
});
},
setupController: function(controller, model) {
this.controllerFor('user').set('content', model.user);
}
When user losts authorization, and you open the page. ApplicationRoute::model is fired first, server returns 401 and other execution is stopped.
GET http://localhost:8000/app_dev.php/api/1/users.json 401 (Unauthorized)
Error while loading route: undefined
model should be fired only when authentication is successfull.
I saw that there is sessionAuthenticationSucceeded but I've tried all the ways to listen to it, noone worked. How to listen to this event and get data from server when user is successfully authenticated?
11/06 22:57 UPDATE:enter code here
One solution for this problem that I've managed to achieve, but it seems totally not ember way:
App.ApplicationRoute = Ember.Route.extend(Ember.SimpleAuth.ApplicationRouteMixin, {
skipModelLoading: false,
beforeModel: function() {
this.set('skipModelLoading', !this.get('session').get('isAuthenticated'));
},
model: function () {
if (this.get('skipModelLoading')) {
return;
}
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function(data) {
return data.get('content')[0]
})
});
},
setupController: function(controller, model) {
if (this.get('skipModelLoading')) {
return;
}
this.controllerFor('user').set('content', model.user);
}
});
I assume you're loading the authenticated user in that model method. I'd do it differently and attach that property to the session as shown in this example: https://github.com/simplabs/ember-simple-auth/blob/master/examples/4-authenticated-account.html#L101
I think I found a more ember-way solution for my problem:
App.ApplicationRoute = Ember.Route.extend(Ember.SimpleAuth.ApplicationRouteMixin, {
onSessionIsAuthenticated: function () {
var isAuthenticated = this.get('session').get('isAuthenticated');
if (!isAuthenticated) {
return false;
}
var userController = this.controllerFor('user');
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function (data) {
userController.set('content', data.get('content')[0]);
})
});
}.observes('session.isAuthenticated').on('init')
});