I am using ember-simple-auth (and ember-simple-auth-token, but I think this is not relevant). Whenever a logged-in user reloads the page, the session is properly re-created. I have one problem, though: when the user enters the credentials, I am running this code:
// controllers/login.js
import Ember from 'ember';
import helpers from '../lib/helpers';
function refreshActiveUser(store, session) {
store.find('user', 'me').then(function(user) {
Ember.set(session, 'activeUser', user);
});
}
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var identification = this.get('model.identification'),
password = this.get('model.password'),
authenticator = 'authenticator:token',
store = this.get('store'),
session = this.get('session');
session.authenticate(authenticator, {identification, password}).then(function() {
refreshActiveUser(store, session);
});
}
},
});
But I do not know how to trigger my refreshActiveUser on application reload. How can I listen to a "session initialized" event?
If I understand your issue correctly then you could make an observer for session.isAuthenticated
loggedIn: function() {
if(!this.get('session.isAuthenticated'))
return;
var store = this.get('store');
var profileLoaded = get_personal_settings(store);
var self = this;
profileLoaded.then(function(profile) {
// TODO: only used for user, save user, not the whole profile
self.set('session', profile);
});
}.observes('session.isAuthenticated').on('init'),
Related
I'm trying to write an acceptance test to see if a certain property in the model for the route I visit equals what I am asserting.
I am not outputting information to the page with this route, instead I will be saving some portion of it to localstorage using an ember addon. So normally I realize I could use a find() to find an element on the page and check it's content to determine if the model is being resolved but that won't work for this case.
In the acceptance test I have this setup (using mirage btw)
test('Returns a user', function(assert) {
// Generate a user
var user = server.create('user',{first_name: 'Jordan'});
// Visit the index page with the users short_url
visit('/' + user.short_url);
var route = this.application.__container__.lookup('route:index');
// Assert that the model the user we created by checking the first name we passed in
assert.equal(route.model.first_name,'Jordan','Model returns user with first name Jordan');
});
But when I run the test it shows the result as being undefined
UPDATE:
After trying Daniel Kmak's answer I still cannot get it to pass. This is the route code I am working with
import Ember from 'ember';
import LocalUser from 'bidr/models/user-local';
export default Ember.Route.extend({
localUser: LocalUser.create(),
navigationService: Ember.inject.service('navigation'),
activate() {
this.get('navigationService').set('navigationMenuItems', []);
},
beforeModel() {
this.localUser.clear();
},
model(params) {
var self = this;
return this.store.queryRecord('user',{short_url: params.short_url}).then(function(result){
if(result){
self.set('localUser.user', {
"id": result.get('id'),
"first_name": result.get('first_name'),
"active_auction": result.get('active_auction'),
"phone": result.get('phone')
});
// transition to event page
self.transitionTo('items');
} else {
self.transitionTo('home');
}
});
}
});
And the test looks like this
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'bidr/tests/helpers/start-app';
module('Acceptance | index route', {
beforeEach: function() {
this.application = startApp();
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('Returns a user', function(assert) {
var user = server.create('user',{first_name: 'Jordan'});
visit('/' + user.short_url);
var route = this.application.__container__.lookup('route:index');
andThen(function() {
assert.equal(route.get('currentModel.first_name'),'Jordan','Model returns user with first name Jordan');
});
});
All the code works as it should in development.
Ok, so I've experimented with testing in Ember and it seems you should be good with getting model in andThen hook:
test('returns a user', function(assert) {
visit('/'); // visit your route
var route = this.application.__container__.lookup('route:index'); // find your route where you have model function defined
andThen(function() {
console.log(route.get('currentModel')); // your model value is correct here
assert.equal(currentURL(), '/'); // make sure you've transitioned to correct route
});
});
Taking your code it should run just fine:
test('Returns a user', function(assert) {
var user = server.create('user',{first_name: 'Jordan'});
visit('/' + user.short_url);
var route = this.application.__container__.lookup('route:index');
andThen(function() {
assert.equal(route.get('currentModel.first_name'),'Jordan','Model returns user with first name Jordan');
});
});
Another thing to note is that you can access model via route.currentModel property.
For my model:
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
simple: 'simpleValue',
promise: Ember.RSVP.resolve(5)
});
}
});
In andThen with console.log(route.get('currentModel')); I got:
Object {simple: "simpleValue", promise: 5}
Logged.
My authenticators/custom.js:
import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
export default Base.extend({
restore: function(data) {
},
authenticate: function(email, password, authenticateCallback) {
return new Ember.RSVP.Promise((resolve, reject) => {
Ember.$.ajax({
type: 'POST',
url: apiOrigin + '/api/v1/login',
data: {
email: email,
password: password
},
dataType: 'json'
}).then(function(userData){
console.log('login post success', userData)
authenticateCallback(userData)
Ember.run(function() {
resolve(userData.uuid)
})
})['catch'](function(main){
alert('login error ' + JSON.stringify(main))
console.error('\'caught\' error from login post request', arguments);
})
})
},
invalidate: function(data) {
}
});
And login/controller.js:
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
application: Ember.inject.controller(),
actions: {
authenticate() {
let { identification, password } = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:custom', identification, password, (userData) => {
//TODO set these properties on ember-simple-auth's session object instead of application controller
this.get('application').setProperties(userData)
this.transitionToRoute('associate-device')
}).catch((reason) => {
this.set('errorMessage', reason.error);
})
}
}
});
My associate-device route is an AuthenticatedRoute.. I don't get an error, but instead, the last thing printed to the console is "Preparing to transition from 'login' to 'associate-device'"
Basically, ember simple auth documents here http://ember-simple-auth.com/api/classes/BaseAuthenticator.html#method_authenticate that "A resolving promise will result in the session becoming authenticated. Any data the promise resolves with will be saved in and accessible via the session service's data.authenticated property (see data). A rejecting promise indicates that authentication failed and will result in the session remaining unauthenticated."
However, my session does not seem to be authenticated after I successfully resolve my promise.
$.ajax has no catch method. This exception was hidden because I was copy-pasting away from the documentation for writing custom authenticators. To expose any exceptions occurring in your custom authenticators authenticate method, you should probably console.log them like so:
// app/controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate() {
let { identification, password } = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:oauth2', identification, password).catch((reason) => {
// **CHANGE THE BELOW LINE**
console.error('exception in your authenticators authenticate method', reason)
});
}
}
});
I'm trying to test my registration and login processes, the integration tests were passing perfectly prior to creating an initializer to extend the Ember-Simple-Auth Session object with the currentUser property.
It all works correctly in the browser, its just the tests now fail all in the sessionAuthenticationSucceeded action in the application route on the following line:
this.get('session.currentUser').then(function(user) {
with : TypeError: Cannot read property 'then' of undefined
/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
actions: {
sessionAuthenticationSucceeded: function () {
var self = this;
this.get('session.currentUser').then(function(user) {
if (user.get('account') && user.get('status') === 'complete'){
self.transtionTo('home');
} else {
console.log('Need to complete Registration');
self.transitionTo('me');
}
});
}
}
}
/initializers/custom-session.js
import Ember from 'ember';
import Session from 'simple-auth/session';
export default {
name: 'custom-session',
before: 'simple-auth',
initialize: function(container) {
// application.deferReadiness();
Session.reopen({
currentUser: function() {
var id = this.get('user_id');
if (!Ember.isEmpty(id)) {
console.log('getting the current user');
return container.lookup('store:main').find('user', id);
}
}.property('user_id')
});
// application.advanceReadiness();
}
};
/tests/integration/visitor-signs-up-test.js
test('As a user with valid email and password', function(){
var email = faker.internet.email();
signUpUser(email, 'correctpassword', 'correctpassword');
andThen(function(){
equal(find('#logged-in-user').text(), email, 'User registered successfully as ' + email);
equal(sessionIsAuthenticated(App), true, 'The session is Authenticated');
});
});
test/helpers/registration-login.js
export function signUpUser(email, password, passwordConfirmation) {
visit('/register').then(function(){
fillIn('input.email', email);
fillIn('input.password', password);
fillIn('input.password-confirmation', passwordConfirmation);
click('button.submit');
});
}
I have tried using
application.deferReadiness()
as you can see commented out in the initializer (also pass in application in that instance) to ensure the async request has completed and user is available but that hasn't worked either.
I am using Pretender to intercept the api requests, but the call to api/v1/users/:id isn't being made at all during the tests.
The strange part is it works perfectly in the browser.
I'm trying to understand why this won't this work? Any guidance would be appreciated!
NB: I have also tried solution listed here and here with same outcome as above.
I have figured out the problem, turns out I wasn't returning a user_id from the api/v1/users/sign_in request Pretender was intercepting hence when sessionAuthenticationSucceeded fired, there was no user_id available and thus currentUser was never being updated/triggered.
I'll leave all the code up there in case it helps somebody else. Comments or improvements to it are still very welcome!
In my EmberJS application, I have a current user initializer, which injects the user into all controllers, routes, and views. It works great when logged in. I need the synchronous loading of the current user object so I can check some user permissions right off the bat.
Here's my initializer:
App.CurrentUserInitializer - Ember.Initializer.extent({
name: 'current-user',
after: 'authentication',
initialize: function(container, app) {
var store = container.lookup('store:main');
app.deferReadiness();
store.find('user', 'me').then(function (user) {
app.register('session:user', user, {instantiate: false});
_.forEach(['route', 'controller', 'view'], function (place) {
app.inject(place, 'currentUser', 'session:user');
});
app.advanceReadiness();
}).catch(function () {
app.advanceReadiness();
});
}
});
Where this breaks down for me, is during login. When the app boots, the initializer is run, but the /users/me route returns a 401 error. If I don't catch the error and advanceReadiness, booting halts. By catching the error, the app boots, but the initializer isn't run again after login, so the current user isn't loaded.
What are my options here? I can't use #marcoow's recommended method of adding a computed property to the Session, as I need the user loaded at boot time.
I've tried forcing loading the user object on the IndexRoute as a hack, but that doesn't seem to work.
Any tips are appreciated.
I'd register a session:current object with user property being null. That would be injected into controllers and routes (not sure injecting inside views is a good idea).
So at boot time the user is unknown, but the user lookup is done before the router goes deeper than application route, the root:
In the beforeModel hook of the application route, you'll load that current user. Then:
either you got the user and you set it this.set('session.user', model)
or you'll go in the error hook of the application route, in which case you'd have to check why, and if 401 then you can redirect the user to the login route this.transitionTo('login')
Don't forget to set a flag on session if you got the 401 so that transitionTo will make our user lookup of beforeModel to not happen again until we reach the login route
The code to be used to load the session user and initialise it could be placed in that session:current object so that you could call it from the application route or the login controller.
This is for example my session initialiser (not exactly as I explained, but loading in the initialiser, so closer to what you hve). I used a session model so that I do /session/current and then got a user into it (or not) which has the correct id and not me which then would make the store to load the same user with another id, and so have twice the same user as 2 different records:
app/models/session.js:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
user: DS.belongsTo('user'),
isAuthenticated: Ember.computed.bool('user.isClaimed')
});
app/initializers/session.js:
import Ember from 'ember';
export default {
name: 'session',
after: 'store',
initialize: function (container, app) {
var store = container.lookup('store:main'),
sid = Ember.$.cookie('sid');
// used to register a session
var register = function (session) {
app.register('session:main', session, {instantiate: false});
app.inject('route', 'session', 'session:main');
app.inject('controller', 'session', 'session:main');
};
// used to create a new session and trigger the backend to get details about it
// useful if the server is able to give an existing session while the browser doesn't know about it
// with external providers for example
var newSession = function () {
var session = store.createRecord('session');
// be sure to wipe out any invalid session ID
Ember.$.removeCookie('sid');
register(session);
return session.save().then(function (model) {
// if we got a valid new session, save its ID
Ember.$.cookie('sid', model.get('id'));
}).catch(function () {
Ember.debug('error saving new session: ' + Array.prototype.join.call(arguments, ', '));
});
};
// overall logic ==================
app.deferReadiness();
if (sid) {
// try to load the existing session
store.find('session', sid).then(function (model) {
register(model);
app.advanceReadiness();
}).catch(function () {
// there was a cookie for the session but it might have expired or the server invalidated it
Ember.debug('error loading session: ' + Array.prototype.join.call(arguments, ', '));
newSession().finally(function () {
app.advanceReadiness();
});
});
}
else {
// we don't have any trace of a session, let's just create a new one
newSession().finally(function () {
app.advanceReadiness();
});
}
}
};
app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend();
Router.map(function () {
this.resource('session', {path: '/'}, function(){
this.route('login');
this.route('logout');
});
});
export default Router;
app/templates/application.hbs (as an example):
<h2 id='title'>Welcome to my app</h2>
{{#if session.isAuthenticated}}
<a {{action 'session.logout'}}>Logout</a>
{{else}}
{{#link-to 'session.login'}}Login{{/link-to}}
{{/if}}
{{outlet}}
Then once in login controller, when the user actually logs in, the server will return the session model with the user linked into it, so the Ember binding magic will just update the session object.
I would like to implement authentication with ember.js.
So, when the application start, before the Router handles the requested url, I want to check the user status. If the user isn't authenticated, I want to save the requested url and redirect to a specific url (/login).
I tried to implement this overloading Ember.Route but I don't think it's a good practice.
For example, if i do:
var AuthRoute = Ember.Route.extend({
redirect: function() {
var controller = App.userController;
if (!controller.get("userAuth")) {
controller.set("lastFilter", this.routeName);
this.transitionTo("index");
}
}
}
});
If, the url is '/admin/foobar', the admin route will redirect instead of foobar.
Can I process redirection before the Router to start?
I use something like this
Ember.SecureRoute = Ember.Route.extend({
role: null,
redirect: function (model) {
if (!this.controllerFor('login').get('authenticated')) {
this._routeToLogin();
}
var role = this.get('role');
if (!Ember.isEmpty(role) && !this.controllerFor('login').hasRole(role)) {
this._routeToLogin();
}
},
_routeToLogin: function () {
var infos = this.get('router.router.currentHandlerInfos');
this.router.router.didTransition(infos);
var routeName = !this.router.router.hasRoute(this.routeName) ? this.routeName + '.index' : this.routeName;
var params = infos.filter(function (item, index, enumerable) { return item.context !== undefined; }).map(function (item) { return item.context; })
var url = Ember.Router.prototype.generate.apply(this.router, params.insertAt(0, routeName))
this.router.location.setURL(url);
this.transitionTo("login");
}
});
in your loginController you can then use the browser history to go back to your original route
APP.LoginController = Ember.Controller.extend({
//other stuff
authenticate: function (username, password) {
//do the authentication
history.go(-1);
}
});
The way that I do it is I pass down the authenticated user with my data. and I have an initConfiguration function inside of my main App
so inside of index file (in this case I am showing you jade) I have this:
// initialize ember settings
script(type='text/javascript')
App.initConfiguration('!{currentUser}')
and inside of my App file I have (coffeescript here)
window.App = Ember.Application.create
currentUser: null
initConfiguration: (currentUser) ->
if currentUser? and currentUser.length > 0
#set 'currentUser', currentUser
If you are using ember-data, then you have to change the application file to
window.App = Ember.Application.create
currentUser: null
tempCurrentUser: null
initConfiguration: (currentUser) ->
##
# set the tempCurrentUser to the currentUser passed in, this is then
# set in the ApplicationRoute to the App.currentUser
# and destroyed (this is necessary as the ember store is not initialzed yet
# and just setting it here will result in an error)
if currentUser? and currentUser.length > 0
#set 'tempCurrentUser', currentUser
and then inside your application route do the following
App.ApplicationRoute = Ember.Route.extend
setupController: (controller) ->
if App.tempCurrentUser?
App.setCurrentUser(App.tempCurrentUser)
Ember has a fantastic guide on preventing and retrying authentication: http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
A simple way to do transitions based on whether or not a user is logged in:
App.ApplicationRoute = Ember.Route.extend({
willTransition: function () {
var session = this.controllerFor('session');
if (!session.get('authed')) {
this.transitionTo('login');
}
}
});
The example above assumes you have some kind of session controller or object managing the active sessions. This works because the ApplicationRoute is the very first route that is hit whenever you enter your application (from any URL).