I'm new to Ember.js and I'm using Ember Simple Auth and I'm having a hard time trying to figure out how to get the current user that is logged in and then checking if the user is an admin so I can display admin only thinks in templates. Currently I am using jwt authentication using Ember Simple Auth Token and a Ruby on Rails backend. Any help in pointing me in the right direction would be great.
I've currently tried getting the example on the Ember Simple Auth to work
https://github.com/simplabs/ember-simple-auth/blob/master/guides/managing-current-user.md
When the user authenticates the jwt is returning the token and the user_id. The issue is that I'm not getting the name or any details about the user when the user is logged in.
I'm trying to access the current user (Which might be wrong) by doing this
{{currentUser.user.name}}
controller/application.js
import Ember from 'ember';
const { inject: { service }, Component } = Ember;
export default Ember.Controller.extend({
session: service('session'),
currentUser: service('current-user')
});
routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
const { service } = Ember.inject;
export default Ember.Route.extend(ApplicationRouteMixin, {
currentUser: service(),
beforeModel() {
return this._loadCurrentUser();
},
sessionAuthenticated() {
this._super(...arguments);
this._loadCurrentUser().catch(() => this.get('session').invalidate());
},
_loadCurrentUser() {
return this.get('currentUser').load();
}
});
services/current-user.js
import Ember from 'ember';
const { inject: { service }, isEmpty, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
load() {
return new RSVP.Promise((resolve, reject) => {
let userId = this.get('session.data.authenticated.user_id');
if (!isEmpty(userId)) {
return this.get('store').find('user', userId).then((user) => {
this.set('user', user);
}, reject);
} else {
resolve();
}
});
}
});
You need to call findRecord
load() {
return new RSVP.Promise((resolve, reject) => {
let userId = this.get('session.data.authenticated.user_id');
if (!isEmpty(userId)) {
return this.get('store').findRecord('user', userId).then((user) => {
this.set('user', user);
resolve();
}, reject);
} else {
resolve();
}
});
}
Related
I'm writing my first question here sorry for any ambiguity.
I write an integration test for update-pw component which simple render update-pw and then fill input field with fillIn and then click save button which trigger the action savePW in update-pw.js. I only pass email(for whom we want to change password) and new password.
savePW() function further has a function call self.store.updateSingleUserPw(email, newPw) which is written in service store.js.
updateSingleUserPw(email, newPw) returns a promise after server process on API call. On basis of fulfillment or rejection of promise I show a modal.
I just want to make that promise fulfill or rejected in my test instead of server response for promise.
// integration/component/update-pw-test.js
import { module, test } from 'qunit';
import EmberObject from '#ember/object';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '#ember/service';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
const store = Service.extend({
savePW() {
self.store.updateSingleUserPw(email, newPw, function() {
console.log('this is function overriding', email, newPw);
return true;
})
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw.',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
});
test('it renders', async function(assert) {
this.application.register('service:store', store);
this.application.inject.service('store', { as: 'store' });
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = EmberObject.create({
username: 'steinauer',
email: 'lala#test.at'
});
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
// assert.equal(this.element.textContent.trim(), 'what is this');
});
});
// components/update-pw.js
import Component from '#ember/component';
export default Component.extend({
changingPassword: false,
actions: {
savePW() {
let self = this;
if (!self.get('currentUpdateAdmin.email'))
return;
let newPw = self.get('password');
let email = self.get('currentUpdateAdmin.email');
self.set('changingPassword', true);
if (!email)
return;
self.store.updateSingleUserPw(email, newPw)
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
}
});
function in Service/store.js :
updateSingleUserPw(email, newPw) {
let headers = this.get('headers');
return new Promise(function(resolve, reject) {
$.ajax({
type: 'POST',
url: ENV.api + '/accounts/updateSingleUserPw',
data: {
email: email,
pwNew: newPw
},
headers,
dataType: 'json'
}).then(function(success) {
if (success) {
resolve(newPw);
} else {
reject('password change failed');
}
}, function(xhr, status, error) {
reject(error);
});
});
}
Before trying to override function I got only rejected promise modal but after the try of overriding the function i'm getting:
Promise rejected during "it renders": Cannot read property register of undefined.
thanks for your question 🎉
Firstly can I thank you for providing your code samples, I would not have been able to solve your question had you not provided so much! I have actually simplified some of the things that you are trying to do and I think by simplifying things I have come to the solution.
Firstly I have renamed the Service that you keep using to be called password-store. Usually when an Ember developer sees a Service named store they tend to think of an ember-data store which I'm assuming you're not actually using here by the functionality that you are expecting.
I generated a very simple mock store that just had one function in it:
// app/services/password-store.js
import Service from '#ember/service';
export default Service.extend({
updateSingleUserPw(email, password) {
// TODO: do something with email & password
return Promise.resolve();
}
});
This just returns a promise so that it won't break any of the other code samples. I then updated your update-pw component to use the new password store:
// app/components/update-pw.js
import Component from '#ember/component';
import { inject as service } from '#ember/service';
function swal() {
// noop - not sure where this comes from
}
export default Component.extend({
passwordStore: service(),
changingPassword: false,
actions: {
savePW() {
if (!this.get('currentUpdateAdmin.email'))
return;
let newPw = this.get('password');
let email = this.get('currentUpdateAdmin.email');
this.set('changingPassword', true);
if (!email)
return;
this.passwordStore.updateSingleUserPw(email, newPw)
.then(() => {
// Reset controller fields
this.set('password', '');
this.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, () => {
this.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(() => {
this.set('changingPassword', false);
});
}
}
});
I also added a swal() function because I didn't quite know where that came from in your example. It seemed to be missing so I just ignored it.
Now lastly I have setup a template so that the test will actually pass:
// app/templates/components/update-pw.hbs
<h4>set new PW for steinauer</h4>
{{input id="password" value=password}}
<button type="button" name="button" class="save-button" {{action 'savePW'}}></button>
Now with the application fully setup here is the full example of a test that will do exactly what you were hoping to do:
// tests/integration/components/update-pw-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import StoreService from 'your-app-name/services/password-store';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
const passwordStore = StoreService.extend({
updateSingleUserPw(email, newPw) {
console.log('updateSingleUserPw override!!');
assert.equal(newPw, 'test123456');
return Promise.resolve();
}
});
this.owner.register('service:password-store', passwordStore);
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = {
username: 'steinauer',
email: 'lala#test.at'
};
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
});
});
The first thing that you might notice is that we are not using this.application.register or this.application.inject. I can't remember exactly if this is how it used to be done a long time ago but this is not available for a few years in Ember.
What we end up doing is we import the StoreService from your-app-name/services/password-store (replacing your-app-name with whatever your modulePrefix is) and then we extend it while overriding the updateSingleUserPw() function. In your example it looked like you were trying to override a function called savePW() but that is actually the action name from the component and it might have been slightly confusing you.
I hope that helps, I have tested the example locally and it works perfectly well! You may also notice I added an assertion inside the service, this is quite a useful pattern to make sure that the service receives the right arguments from the component 👍
In my implementation below I try get the token from ember-simple-auth-token and use with a URL, used to connect with ActionCable:
...
export default Route.extend({
store: service(),
currentUser: service(),
session: service(),
cable: service(),
setupConsumer: on('init', function() {
let token = this.get('session.data.authenticated.jwt');
let consumer = this.get('cable')
.createConsumer(`wss://api.${config.APP.host}/cable?token=${token}`);
let channelMixin = Mixin.create({
received(data) {
this.get('store').pushPayload(data);
}
});
consumer.subscriptions.create({
channel: 'ChatroomsChannel'
}, channelMixin);
}),
...
});
This works only at first request. I need store this in a cookie? Thanks.
The follow code solves my issue:
import Route from 'ember-route';
import service from 'ember-service/inject';
import Mixin from 'ember-metal/mixin';
import config from 'apollo-enterprise/config/environment';
export default Route.extend({
session: service(),
cable: service(),
afterModel(model) {
this.get('session.store').restore().then((data) => {
let token = data.authenticated.jwt;
let consumer = this.get('cable')
.createConsumer(`wss://api.${config.APP.host}/cable?token=${token}`);
let channelMixin = Mixin.create({
store: service(),
received(data) {
this.get('store').pushPayload(data);
}
});
consumer.subscriptions.create({
channel: 'MessagesChannel',
chatroom_id: model.id
}, channelMixin);
});
},
setupController(controller) {
this._super(...arguments);
controller.set('message', {});
},
actions: {
sendMessage(params) {
let chatroom = this.controller.get('model');
let message = this.get('store').createRecord('message', params);
message.set('chatroom', chatroom);
message.save().then(() => {
this.controller.set('message', {});
});
}
}
});
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'))
I am updating my existing code done in ember-simple-auth: 0.8.0 to ember-simple-auth: 1.0.1
There are two problems
It is not persisting a session
REST Calls needed to be having withCredentials: true, not sure where I can set them.
Here is my code
//config/environment.js
ENV['ember-simple-auth'] = {
store: 'simple-auth-session-store:local-storage',
authorizer: 'authorizer:custom',
routeAfterAuthentication: '/dashboard',
routeIfAlreadyAuthenticated: '/dashboard'
};
My authenticator
//authenticators/custom.js
import Ember from 'ember';
import Base from 'ember-simple-auth/authenticators/base';
import config from '../config/environment';
export default Base.extend({
restore(data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
}
else {
reject();
}
});
},
authenticate(options) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "POST",
url: config.serverURL + '/api/users/login',
data: JSON.stringify({
username: options.identification,
password: options.password
}),
contentType: 'application/json;charset=utf-8',
dataType: 'json'
}).then(function(response) {
Ember.run(function() {
resolve(response);
});
}, function(xhr) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
},
invalidate(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "POST",
url: config.serverURL + '/api/users/logout'
}).then(function(response) {
Ember.run(function() {
resolve(response);
});
}, function(xhr) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
}
});
My authorizer (you can see that I am trying to update my old code)
//authorizers/custom.js
import Ember from 'ember';
import Base from 'ember-simple-auth/authorizers/base';
export default Base.extend({
authorize(sessionData, block) {
if (!Ember.isEmpty(sessionData.token)) {
block('X-CSRF-Token', sessionData.token);
block('Content-Type', 'application/json;charset=utf-8');
block('withCredentials', true);
}
}
//authorize(jqXHR, requestOptions) {
// if (!(requestOptions.data instanceof FormData)){
// requestOptions.contentType = 'application/json;charset=utf-8';
// }
//
// requestOptions.crossDomain = true;
// requestOptions.xhrFields = {
// withCredentials: true
// };
//
//
// var token = this.get('session.token');
// console.error(jqXHR);
// if (this.get('session.isAuthenticated') ) {
// jqXHR.setRequestHeader('X-CSRF-Token', token);
// }
//}
});
My application adapter
import DS from 'ember-data';
import config from '../../config/environment';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
export default DS.RESTAdapter.extend(DataAdapterMixin, {
authorizer: 'authorizer:custom',
namespace: 'api',
host: config.serverURL,
});
Dashboard
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
session: Ember.inject.service('session'),
needs: 'application',
setupController: function(controller, model){
this.controllerFor('application').set('pageTitle', 'Dashboard');
this._super(controller, model);
}
});
If I do console.log(this.get('session.isAuthenticated'); it returns me true, but when I use that in template it dont work
{{#if session.isAuthenticated}}
1
{{else}}
0
{{/if}}
On my laravel end, i can see that session is created and user is logged in, on Ember side, it was previously setting the session and then resends the credentials with each request. Now when it send another request. I think it is without credentials: True and laravel returns 401. I also tried sending a garbage header and laravel CORS refused that it is not in allowed headers.
Thank you
The authorizer config setting doesn't exist anymore in 1.0 as auto-authorization has been dropped. See the API docs for info on how to add authorization to outgoing requests:
http://ember-simple-auth.com/api/classes/SessionService.html#method_authorize
http://ember-simple-auth.com/api/classes/DataAdapterMixin.html
Also your authorizer should not call the block several times but only ones, passing all authorization data at once.
Also make sure you inject the session service into all controllers and components for templates you use the session in.
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.