ember-cli custom authenticator simple auth session authentication failed - ember.js

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.

Related

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 throwing Unexpected Token error

Undoubtedly this error is something easy for an ember expert to identify but thats not me so here it is
Ember-cli identifies blank space before this line as an unexpected token:
this.store = container.lookup('store:main');
/*global md5*/
import Ember from 'ember';
// Since I've defined my url in environment.js I can do this
import ENV from '../config/environment';
var ref = new window.Firebase(ENV.firebaseURL);
export default {
name: 'session',
// Run the initializer after the store is ready
after: 'store',
initialize: function(container, app) {
// session object is nested here as we need access to the container to get the store
var session = Ember.Object.extend({
// initial state
authed: false,
// get access to the ember data store
//Here is the offending line
this.store = container.lookup('store:main');
init: function() {
// on init try to login
ref.onAuth(function(authData) {
// Not authenticated
if (!authData) {
this.set('authed', false);
this.set('authData', null);
this.set('user', null);
return false;
}
// Authenticated
this.set('authed', true);
this.set('authData', authData);
this.afterAuthentication(authData.uid);
}.bind(this));
},
// Call this from your Ember templates
login: function(provider) {
this._loginWithPopup(provider);
},
// Call this from your Ember templates
logout: function() {
ref.unauth();
},
// Default login method
_loginWithPopup: function(provider) {
var _this = this;
// Ember.debug('logging in with popup');
ref.authWithOAuthPopup(provider, function(error, authData) {
if (error) {
if (error.code === "TRANSPORT_UNAVAILABLE") {
// fall-back to browser redirects, and pick up the session
// automatically when we come back to the origin page
_this._loginWithRedirect(provider);
}
} else if (authData) {
// we're good!
// this will automatically call the on ref.onAuth method inside init()
}
});
},
// Alternative login with redirect (needed for Chrome on iOS)
_loginWithRedirect: function(provider) {
ref.authWithOAuthRedirect(provider, function(error, authData) {
if (error) {
} else if (authData) {
// we're good!
// this will automatically call the on ref.onAuth method inside init()
}
});
},
// Runs after authentication
// It either sets a new or already exisiting user
afterAuthentication: function(userId) {
var _this = this;
// See if the user exists using native Firebase because of EmberFire problem with "id already in use"
ref.child('users').child(userId).once('value', function(snapshot) {
var exists = (snapshot.val() !== null);
userExistsCallback(userId, exists);
});
// Do the right thing depending on whether the user exists
function userExistsCallback(userId, exists) {
if (exists) {
_this.existingUser(userId);
} else {
_this.createUser(userId);
}
}
},
// Existing user
existingUser: function(userId) {
this.store.find('user', userId).then(function(user) {
_this.set('user', user);
}.bind(this));
},
// Create a new user
createUser: function(userId) {
var _this = this;
this.get('store').createRecord('user', {
id: userId,
provider: this.get('authData.provider'),
name: this.get('authData.facebook.displayName') || this.get('authData.google.displayName'),
email: this.get('authData.facebook.email') || this.get('authData.google.email'),
created: new Date().getTime()
}).save().then(function(user){
// Proceed with the newly create user
_this.set('user', user);
});
},
// This is the last step in a successful authentication
// Set the user (either new or existing)
afterUser: function(user) {
this.set('user', user);
}
});
// Register and inject the 'session' initializer into all controllers and routes
app.register('session:main', session);
app.inject('route', 'session', 'session:main');
app.inject('controller', 'session', 'session:main');
}
};
You're calling Ember.Object.extend with an Javascript Object literal what you are trying to do is invalid javascript syntax.
You'll probably want to stick that line in your init function.
init: function() {
//Here is the offending line
this.store = container.lookup('store:main');
...
When you get an invalid token error message you're writing something the javascript compiler doesn't understand.

Ember simple auth session content lost after page reload

I've created a clean ember app, installed simple-auth and implemented a custom authenticator for facebook.
https://github.com/prule/ember-auth-spike
I can see that I'm successfully getting the access token from FB and its put in the session (inspecting the container session via chrome ember extension shows me the session is authenticated and the access token is visible).
But when I reload the page in the browser, the session state is lost. Is this expected behaviour? Have I done something wrong in my custom authenticator? The authenticator code is a straight copy and paste (plus some console.logs) from https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-multiple-external-providers.html
Thanks, I appreciate any help.
import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
export default Base.extend({
restore: function (data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
console.log('restore');
if (!Ember.isEmpty(properties.accessToken)) {
console.log('found access token '+properties.accessToken);
resolve(properties);
}
else {
console.log('no token found');
reject();
}
});
},
authenticate: function (options) {
return new Ember.RSVP.Promise(function (resolve, reject) {
console.log('1');
FB.getLoginStatus(function (fbResponse) {
console.log('2');
console.log(fbResponse);
if (fbResponse.status === 'connected') {
Ember.run(function () {
console.log(fbResponse.authResponse.accessToken);
resolve({accessToken: fbResponse.authResponse.accessToken});
});
}
else if (fbResponse.status === 'not_authorized') {
reject();
}
else {
FB.login(function (fbResponse) {
if (fbResponse.authResponse) {
Ember.run(function () {
console.log(fbResponse.authResponse.accessToken);
resolve({accessToken: fbResponse.authResponse.accessToken});
});
}
else {
reject();
}
});
}
});
});
},
invalidate: function (data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
FB.logout(function (response) {
Ember.run(resolve);
});
});
}
});
The argument to the authenticator's restore method is called data but you're checking for properties.accessToken. This should actually raise an error anyway as properties is undefined there.

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 auth transition.retry() after login doesn't transit while testing

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.