How to verify ACL in a template - ember.js

How should a template lookup the user access to a specific route before displaying a link/action?
Considering the routes already contain a list of authorized roles, should a simple template helper/component lookup the view property and validates access?
( something like {{#if has-access-to 'items.new'}} ? )
Routes are currently "protected" using a simple ACL solution:
AclRouteMixin
import Ember from 'ember';
var accountTypes = {
1: 'member',
2: 'manager',
3: 'owner'
};
export default Ember.Mixin.create({
beforeModel: function beforeModel(transition) {
this._super(transition);
var accountType = this.get('session.accountType');
var role = accountTypes.hasOwnProperty(accountType) ? accountTypes[accountType] : 'unknown';
if (this.get('roles') && !this.get('roles').contains(role)) {
transition.abort();
this.transitionTo('unauthorized');
}
}
});
Route
export default Ember.Route.extend(AuthenticatedRouteMixin, AclRouteMixin, {
roles: [ 'manager', 'owner' ]
});
EDIT
Since the server knows the permissions it is much easier to include a policy object ( or per-entity properties ) than trying to duplicate the authorization logic.

This talk ( linked by MilkyWayJoe ) explains a really simple way to setup authentication / ACL.
The session object ( or each API response ) could contain a Policy object that contains true/false values.
Template
{{#if session.policy.canCreateItems}}{{link-to 'New Item' 'items.new'}}{{/if}}
{{#if item.policy.canEdit}}{{link-to 'Edit' 'items.edit' item}}{{/if}}
Authenticator ( if using ember-simple-auth )
var session = {
userId: response.user_id,
policy: {}
};
for(var p in response.policy) {
if (response.policy.hasOwnProperty(p)) {
session.policy[p.camelize()] = response.policy[p];
}
}
API responses
{
"items": [{
...
policy: {
can_delete: true,
can_view: true,
can_edit: true
}
}],
"policy": {
can_create: true
}
}

The way I would do it is load up the permissions on an auth route that all other routes extend, as for checking it and displaying links I went ahead with a component:
import Ember from 'ember';
var Component = Ember.Component;
export default Component.extend({
hasPermission: function() {
var permission = this.get('permission');
return this.get('auth.permissions').indexOf(permission) !== -1;
}.property('permission')
});
As for the template:
{{#if hasPermission}}
{{yield}}
{{/if}}
And simply call it from links:
{{#can-do permission="view_tables"}}
{{link-to "tables" "tables" class="nav__link"}}
{{/can-do}}
Hope it helps. Let me know if you have any questions.

Related

URL management with Django, GraphQL, Apollo and VueJS

As said in the title, I'm using Django, GraphQL, Apollo and VueJS in my project.
I'm developping it as a SPA (Single Page Application).
Everything works fine, until I hit the F5 button and refresh the page. Indeed, it shows an unknown page. The thing is it is VueRouter that is managing the SPA and it works fine. But when I press F5, that is Django that tries to serve a page for the current URL and since it doesn't know it, it can't serve the appropriate page.
I know I can set the VueRouter 'history' mode, which I did, and add a URL to Django that serves index.html whatever the URL is.
My problem is the following :
When I'm on a particular form view (i.e : a User form view) my URL is the following :
http://localhost:8000/user
Since I'm using GraphQL for my API, the retrieved data is not based on the URL. In fact, that is my VueJS component that says : Hey Apollo, run that GraphQL to retrieve the User I want.
So when I refresh, yes it serves the User form view..but empty.
The question is : How could I solve this ?
For clarification purposes, here are some code samples :
My Django URLs :
# (... other imports here ...)
from .schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))), # 'schema' is the main GraphQL schema
path('', TemplateView.as_view(template_name='index.html')),
re_path(r'^.*$', TemplateView.as_view(template_name='index.html')) # I saw that many times to serve the page whatever the URL is when refreshing the page
]
My Vue Router :
export default new Router({
mode: 'history',
routes: [
{ path: '/', name: 'MainApp' },
// ...
{ path: '/users', name: 'UserList', component: UserList },
{ path: '/user/create', name: 'UserFormCreate', component: UserForm, props: true },
{ path: '/user', name: 'UserFormView', component: UserForm, props: true },
{ path: '/user/edit', name: 'UserFormEdit', component: UserForm, props: true },
// Same pattern for other models like 'Group' ...
]
My Example VueJS Component :
<script>
import {
// ...
USER_QUERY,
// ...
} from '../../graphql/base/user.js'
export default {
name: 'UserForm',
props: {
userId: Number,
editing: {
type: Boolean,
default: false
}
},
apollo: {
user: {
query: USER_QUERY,
variables () { return { id: this.userId } },
skip () { return this.userId === undefined },
result ({ data }) {
this.form.username = data.user.username
this.form.firstName = data.user.firstName
this.form.lastName = data.user.lastName
}
}
},
data () {
return {
form: {
username: '',
password: '',
firstName: '',
lastName: ''
},
// ...
}
},
methods: {
// ...
}
I have to mention that I've seen more or less related topics but that doesn't solve my problem.
Thanks in advance for your help !
Edit your route paths to use params. For example:
{ path: '/user/:userId', name: 'UserFormView', component: UserForm, props: true }
Now, the app will interpret any number following the user/ path as a prop called userId. (props: true is important here for using the params as props.)
The only other change you need to make is adjusting your router-links to include the id as well (Ex.: http://localhost:8000/user/1) so that when the page is refreshed, there will be a param to read.

Loopback: How to add afterRemote of a model to another model

I have Notification model which looks like this
"use strict";
module.exports = function(Notification) {
};
And I have another model which is Post:
"use strict";
module.exports = function(Post) {
Post.prototype.postLike = function(options, cb) {
this.likes.add(options.accessToken.userId);
cb(null, "sucess");
};
Post.remoteMethod("postLike", {
isStatic: false,
accepts: [{ arg: "options", type: "object", http: "optionsFromRequest" }],
returns: { arg: "name", type: "string" },
http: { path: "/like", verb: "post" }
});
}
What I want is to add afterRemote method of Post inside of notification model ?
Is it possible in loopback ?
It should looks like :
"use strict";
module.exports = function(Notification) {
var app = require("../../server/server.js");
var post = app.models.Post;
post.afterRemote('prototype.postLike', function(context, like, next) {
console.log('Notification after save for Like comment');
});
};
But this does not work.
NOTE: I can do it Post model itself, but I want to add all of my notification logic in Notification model for simplification and future customization.
You can use events to do.
Loopback application emits started event when it started after all boot scripts loaded here
and in Notification model do like this :
"use strict";
module.exports = function(Notification) {
var app = require("../../server/server.js");
app.on('started', function(){
var post = app.models.Post;
post.afterRemote('prototype.postLike', function(context, like, next) {
console.log('Notification after save for Like comment');
});
});
};
Or create a boot script and emit a custom event like 'allModelsLoaded'. So make sure the boot script is the last one to be run. Boot scripts run in alphabetic order by default. So make z.js and emit that custom event there then listen to that event in Notification model.
Loopback boot process first loads models, and then invoke boot scripts once all models have been loaded. If your aim is to consolidate things across models, then it is better to do this in a boot script, rather than in model.js file.

Ember.js Service objects recreated on route transition

As I understand from the documentation a Service is basically a singleton object used to provide services to other objects through the application lifecycle. I have a user management service which I use to save an authentication token after the user logs in using the route /users/login. But transitioning to another route (/composer for instance) causes the service instance to be recreated and hence it loses all the stored data. Doesn't this contradict the fact that it should live as long as the application does or do I have a wrong of idea this whole lifecycle thingy?
I'm injecting the service in all my routes as below:
authenticationService: Ember.inject.service('authentication-service'),
The service itself is only a set of getters and setters:
import Ember from 'ember';
export default Ember.Service.extend({
currentUser: undefined,
jwtToken: undefined,
// ================================================================================================================ \\
// ================================================================================================================ \\
// ================================================================================================================ \\
setCurrentUser(user) {
this.currentUser = user ;
},
getCurrentUser() {
return this.currentUser ;
},
isLoggedIn() {
return Boolean(this.currentUser) ;
},
getJwtToken() {
return this.jwtToken ? this.jwtToken : '' ;
},
setJwtToken(jwtToken) {
this.jwtToken = jwtToken ;
}
});
Here is how the login token is handled:
actions: {
onSubmitLoginForm() {
if (!this.validateLoginForm()) {
return ;
}
var self = this ;
Ember.$.post('login/', {
'username': this.controller.get('username'),
'password': this.controller.get('password'),
'email': this.controller.get('email'),
}, function(data) {
console.log(data) ;
if (data['success'] === 'Ok') {
self.get('authenticationService').setJwtToken(data['auth']['jwt']) ;
var user = self.get('store').createRecord('user', {
username: data['auth']['user']['username'],
email : data['auth']['user']['email'],
mongoId : data['auth']['user']['id']['$oid'],
}) ;
self.get('authenticationService').setCurrentUser(user) ;
self.transitionTo('home') ;
console.log('logged in') ;
console.log(self.get('authenticationService').getJwtToken()) ;
console.log(self.get('authenticationService').getCurrentUser()) ;
console.log(self.get('authenticationService').isLoggedIn()) ;
} else {
self.transitionTo('error') ;
}
}) ;
},
}
I'm not looking for suggestions on using some other means of persistence such as IndexedDB; I'm willing to understand how this thing actually works so any explanation is appreciated.
Yes, you understand it right - service is a singletone and I can assure you that service persists its state between trasitions. But to make a transition, you must use link-to helper. If you are changing url manually, you reloading your app instead of transitioning. And app reload of course causes state reset. You should use any available kind of storage to persist state between page reloads. It may be local storage, session storage, cookies etc.
Also, in Ember we don't use such code: this.currentUser = user ; on Ember objects. We use this.set('currentUser', user); instead. Otherwise Ember would not be able to rerender templates, update computed properties and work properly.
And finally, you shouldn't build auth solution from zero. It's very hard and complex thing to do. Instead, you can use ember-simple-auth addon and build authentication process on top of it. It will be much easier and result will be more reliable.

EmberFire & Firebase Storage Reference

I'm relatively new to Ember and EmberFire. I'm working on a Client/Logo management application. I've currently got Firebase Authentication and Firebase data working as expected. When I go to upload the logo to Firebase storage, I am presented with this error:
Uncaught Error: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp(). firebase.js:30
Here is the action that is being called:
Controller
firebase: Ember.inject.service( 'firebase' ),
actions: {
createClient(){
let name = this.get( 'clientName' );
let image = document.getElementById( 'client-image' );
let storeName = name.replace( / /g, '' );
let storageRef = firebase.storage().ref();
let file = image.files[0];
let metadata = {
'contentType' : file.type
};
let uploadTask = storageRef.child( `uploads/${storeName}/${file.name}` ).put( file, metadata );
uploadTask.on( 'state_changed', null, function( error ){
console.error( 'Upload Failed:', error );
}, function(){
console.log( 'Uploaded', uploadTask.snapshot.totalBytes, 'bytes.' );
console.log( uploadTask.snapshot.metadata );
let uploadUrl = uploadTask.snapshot.metadata.downloadURLs[0];
console.log( 'File available at ', url );
let client = this.store.createRecord( 'client', {
name: name,
image: uploadUrl,
isActive: false,
timestamp: new Date().getTime()
} );
client.save();
} );
// Tell the route to hide the client form.
this.send( 'hideAddClientForm' );
}
}
Template
<div id="new-overlay" class="overlay" {{action "hideAddClientForm"}}></div>
<div id="newClient">
{{input type="text" id="client-name" placeholder="Client Name" value=clientName}}<br />
{{input type="file" id="client-image" value=clientImage}}
<div class="submitBtn" {{action "createClient"}}>Add Client</div>
</div>
So, in short, how do I access the Firebase reference provided by EmberFire so that I can invoke the "storage()" method to it that is shown here in the Quickstart. If access to that reference isn't possible, do I have to create another, "non-EmberFire" reference to Firebase in order to use Storage? I'm simply trying to uploading a .jpg.
Make sure you are using EmberFire 2.0. You can access the Firebase Storage API through the firebaseApp service:
firebaseApp: Ember.inject.service(),
actions: {
doSomething() {
const storageRef = this.get('firebaseApp').storage();
}
}
The firebaseApp service is an already initialized app, not to be confused with the firebase service, which is the database reference only (kept for backwards compatibility).

Emberjs and Validation

How are people handling client side validation and ember?
Is there anything out of the box or a plugin that handles validation or are people just rolling their own?
https://github.com/dockyard/ember-validations might be useful. It also hooks up to Ember-easy-form
I would extend Ember.TextField (or whatever input type your validating) and use classBinding with a computed property. Here is the sample: http://jsfiddle.net/caligoanimus/7UNRd/
template:
<script type="text/x-handlebars" >
{{view App.AlphaNumField
placeholder="alpha-numeric data only"
valueBinding="App.alphaNumInput"}}
</script>
application:
App = Ember.Application.create({
AlphaNumField: Ember.TextField.extend({
isValid: function() {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value'),
classNameBindings: 'isValid:valid:invalid'
})
});
Another fully supported option and very logical if you are using bootstrap is to use bootstrap-validation plugin. In ember (ember-cli) this should be installed using bower:
bower install --save bootstrap-validation
then in ember-cli-build you must import dependencies:
//bootstrap-validator
app.import('bower_components/bootstrap-validator/dist/validator.js');
app.import('bower_components/bootstrap-validator/dist/validator.min.js');
This solution allows you to validate at html level, letting bootstrap do the 'dirty' job. For standard validations this will do the job simple and effortless.
I have been handling it in a very similar way to #caligoanimus, but validating on the focus out of the text box and appending an error message.
code:
App.TextFieldEmpty = Ember.TextField.extend({
focusOut: function() {
var valid = this.get('value') ? valid = true : valid = false;
this.$().next(".err").remove();
if(!valid){
this.$().addClass("invalid").after("<span class='err'>This field is required</span>");
} else {
this.$().removeClass("invalid")
}
}
});
Template:
<script type="text/x-handlebars">
{{view App.TextFieldEmpty}}
</script>
JSBIN:
http://jsbin.com/uriroYO/6/edit?html,js,output
I've had a lot of success with jQuery Validate:
App.MyView = Ember.View.extend({
templateName: 'my-form-template',
didInsertElement: function(){
$("#myForm").validate({
rules:{
fname:{
required: true,
maxlength: 50,
},
lname:{
required: true,
maxlength: 50,
},
email: {
required: true,
email: true,
maxlength: 200,
},
},
messages: {
email: {
email: "Enter a valid email address.",
},
},
});
}
});
Just using the Ember input helper, it's made my form validation very clean. You can take your jQuery Validate script and place it in a .js file as a function and just call that on didInsertElement.
jQuery Validate adds error messages below your fields with the message relating to the rule, and also allows you to trigger validation from any of your actions or events through the .valid() method.
We have created our own text fields which raise validation errors on focus out, and other events and other text fields extend them:
App.PhoneNumberField = App.TextField.extend({
validate: function(value) {
var self = this.$('input');
var id = self.attr("id");
var e164PhoneNumber = formatE164("AU",value);
var valid = true;
if(self.val().trim().length == 0 ){
valid = true;
}else{
valid = isValidNumber(e164PhoneNumber);
}
if (!valid) {
self.trigger(Storm.invalidInputEvent(id));
this.setErrorMessage("error","Phone Number does not look right");
}else {
self.trigger(Storm.validInputEvent(id));
this.clearMessages();
}
},
keyUp: function(evt) {
if(evt.keyCode >= 37 && evt.keyCode <=40)
{
return;
}
var textValue = this.$("input").val();
var input = this.$().find('input');
var formattedNumber = this.formatInput(textValue);
input.val(formattedNumber);
this.set('data',formattedNumber);
},
value: function() {
var phoneNumber = this.get('data');
if (phoneNumber) {
return phoneNumber;
} else {
return "";
}
}.property('data'),
data: null,
placeholder: function() {
return "";
}.property('placeholder'),
formatInput: function(textValue){
var formattedPhoneNumber = formatLocal("AU",textValue);
return formattedPhoneNumber;
}
});
I would use a model / persistance layer which uses a conventional "errors" object to save validation errors on the model.
Since Ember shines when it comes to observing changes, I would observe the changing errors object to determine whether or not should there be shown a validation message.
In my current setup I'm using Tower.js as framework, because it uses Ember as the View layer, and has a strong model / persistance layer. This layer allows me to define validations on model level. Each time I try to persist a model, it is validated and an error is thrown. In my views, this error aborts the "ideal" path and does not keep executing the workflow, instead it renders the validation errors in the template.