using angularjs http-auth with $resource - http

I am trying to make HTTP Auth Interceptor Module work together with $resource.
for now, i have a basic app working with services like this :
angular.module('myApp.services', ['ngResource']).
factory('User', function($resource){
return $resource('path/to/json/', {}, { 'login': { method: 'GET' } });
});
and then controllers like this one :
angular.module('myApp.controllers', []).
controller('User', ['$scope', 'User', function($scope, List)
{ $scope.user = User.query();
]);
and the app :
angular.module('myApp', ['myApp.services', 'myApp.directives', 'myApp.controllers']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/dashboard', {templateUrl: 'partials/dashboard.html', controller: 'Dashboard'});
$routeProvider.when('/user', {templateUrl: 'partials/user.html', controller: 'TrackingCtrl'});
$routeProvider.otherwise({redirectTo: '/dashboard'});
}]);
Until now, everything is working like expected. Then, i put an http auth on the server, so the json files http status are 401 and the browser display a login popup. I would like to use the HTTP Auth Interceptor Module to replace the browser login popup and handle the login. it is the purpose of this script, right ?
To do so, i am trying to understand the demo of the script and make that work with my app.
first, i injected 'http-auth-interceptor' to the app.
then, this was added to index.html
<body ng-controller="UserCtrl" class="auth-demo-application waiting-for-angular">
and the login form above ng-view:
<div id="login-holder">
<div id="loginbox">
<div id="login-inner" ng:controller="LoginController">
<form ng-submit="submit()">
Username: <input type="text" class="login-inp" ng-model="username"/><br/>
Password: <input type="password" class="login-inp" ng-model="password"/><br/>
<input type="submit" class="submit-login"/>
</form>
</div>
</div>
</div>
and the script at the bottom of the page:
<script src="lib/directives/http-auth-interceptor.js"></script>
in controller.js :
controller('LoginController', ['$scope', 'authService', 'User', function ($scope, authService, User) {
$scope.submit = function() {
User.login(
function(user, response) { // success
authService.loginConfirmed();
console.log('login confirmed');
}
);
}
}])
and this directive too :
directive('authDemoApplication', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
//once Angular is started, remove class:
elem.removeClass('waiting-for-angular');
var login = elem.find('#login-holder');
var main = elem.find('#content');
login.hide();
scope.$on('event:auth-loginRequired', function() {
login.slideDown('slow', function() {
main.hide();
});
});
scope.$on('event:auth-loginConfirmed', function() {
main.show();
login.slideUp();
});
}
}
})
Ok… that's it. but it's not working at all: the browser loggin form is still there. and that's the only way to login.
any idea on what i should do to make this work ?

From their webpage:
Typical use case:
somewhere the: $http(...).then(function(response) { do-something-with-response }) is invoked,
the response of that requests is a HTTP 401,
'http-auth-interceptor' captures the initial request and broadcasts 'event:auth-loginRequired',
your application intercepts this to e.g. show a login dialog (or whatever else),
once your application figures out the authentication is OK, you are to call: authService.loginConfirmed(),
your initial failed request will now be retried and finally, the do-something-with-response will fire.
So the only thing you need to do to interact with this is to have a parent scope to listen to even:auth-loginRequired. This can be done in a directive, in a controller, it doesn't matter. I haven't used the software, but I'm imagining something like this:
angular.module('myApp', [])
.service('api', function(httpAutenticationService, $rootScope, $location) {
$rootScope.$on('event:auth-loginRequired', function() {
// For the sake of this example, let's say we redirect the user to a login page
$location.path('/login')
})
$http.get(API_URL).then( ... ); // If the user is not authenticated, this will be intercepted (1)
})
.controller('LoginController', function(httpAutenticationService) {
// Let's imagine you have a route with the 'controller' option to this controller
$http.get(LOGIN_URL).then(function() {
httpAutenticationService.loginConfirmed(); // The success callback from (1) will be executed now
}, function(){
alert("Invalid credentials")
})
})

Related

How to call function every time collection updates from server?

For example, in this controller parties object autoupdates when another user changes Paries collection, but how do we catch this update and run some controller logic?
this.helpers({ parties: () => Parties.find({}) });
SPECIFIED QUESTION:
Recieved answers do not solve my problem as it's not server logic or any database manipulations that I need to perform upon update. Actually it's controller logic that I need to run.
In the following example I disable submit button if form is not changed. isFormChange function compares party with originalParty. I need to redefine originalParty value when party changes from server side. So how can I do this?
<form ng-submit="vm.updateParty()">
<input type="text" ng-model="vm.party.name">
<input type="submit" ng-disabled="!vm.isFormChanged()" value="Submit">
</form>
directive controller:
function Ctrl($scope, $reactive) {
let vm = this;
$reactive(vm).attach($scope);
vm.helpers({ party: () => Parties.findOne({_id: vm.partyId}) });
let originalParty = angular.copy(vm.party);
vm.isFormChanged = isFormChanged;
vm.updateParty = updateParty;
function isFormChanged() {
return !angular.equals(vm.party, originalParty);
}
function updateParty() {
Meteor.call('updateParty', vm.party._id, vm.party.name);
}
}
Use the collection-hooks package to run code before or after updates, upserts, inserts, and removes.
myCollection.after.update(userId, doc, fieldNames, modifier, options){
...your code
}
You can run your logic on the server, if you have your database functionality in Meteor methods.
Example:
Parties = new Mongo.Collection("parties");
if (Meteor.isClient) {
// This code only runs on the client
Meteor.subscribe("parties");
/* a helper or event can run the Meteor.call() */
var partyData = {}; // get partyData
Meteor.call("insertParty", partyData,
// callback function
function (error, result) {
if (error) { console.log(error); };
});
}
if (Meteor.isServer) {
// This code only runs on the server
Meteor.publish("parties", function () {
return Parties.find({});
});
Meteor.methods({
insertParty: function (partyData) {
// insert logic to run on partyData here
Parties.insert(partyData);
}
})
}

Route using wrong controller?

I have 2 controllers (iron-router), one for access bits (login etc.) and one for the logged in area. But for some reason one of my routes is choosing to use the wrong controller, even though I'm explicitly stating which one to use. Here is the code:
// Controllers
AccessController = RouteController.extend({
layoutTemplate: 'AccessMaster',
onBeforeAction: function () {
if (Meteor.user()) { // If user is logged in then take them to the Dashboard
this.redirect('/app/dashboard');
} else {
this.next();
}
}
});
DashboardController = RouteController.extend({
layoutTemplate: 'DashboardMaster',
onBeforeAction: function () {
if (!Meteor.user()) { // If user is not logged in then take them to the login
this.redirect('/app/login');
} else {
this.next();
}
}
});
// Routes
Router.route("/app/signup", {
name: 'Signup',
controller: 'AccessController'
});
Router.route("/app/login", {
name: 'Login',
controller: 'AccessController'
});
Router.route("/app/account", {
name: 'Account',
controller: 'DashboardController',
loadingTemplate: 'Loading',
action: function () {
this.render('Account');
}
});
Router.route("/app/dashboard", {
name: 'Dashboard',
controller: 'DashboardController',
loadingTemplate: 'Loading',
waitOn: function () {
…
},
action: function () {
this.render('Dashboard', {
data: {
…
}
});
}
});
When I visit app/account I'm redirected to app/dashboard, as directed in the AccessController. Why is the app/account route using the wrong controller logic?
Edit: Oddly, if I remove the controller declaration in the offending route (controller: 'DashboardController') then the template loads fine. So it only uses the wrong controller when I ask it to us a controller.
I must be missing something but that's awfully odd.
I think that your problem comes from the fact that you are using Meteor.user() in both controllers, which is the actual user document. And like any other collection it may not be immediately ready when the application starts.
If you add a console.log(Meteor.user()) in your controllers, you will see that it is first briefly undefined before returning the user document.
So the route is using the right controller but Meteor.user() is undefined so you are redirected to /app/login where Meteor.user() (probably ready now) returns the documents so you get redirected to /app/dashboard.
To prevent such behavior I use Meteor.userId() which is always available no matter what. And I only use Meteor.user() when I have first tested that Meteor.userId() returned something and if I need more information about the user.

Prevent client from calling server-side methods

so I have a login form that accepts a username and password. When a username/password is entered and submit is clicked, the first step is to check if the account exists and is enabled. I've accomplished that using the code below. The problem is, the server-side method that does the checking, is_user_enabled, can be accessed by the client via the browser console. Usually I can prevent this by doing:
my_method : function(doc) {
if (is_admin()) {
// Only admins can run this method.
}
}
But in the case of is_user_enabled, the user is not logged in yet. So, my question is, what is the correct way to handle this situation?
My code:
client/login.html
{{#autoForm schema=get_login_form_schema id="login_form"}}
{{> flashMessages}}
<fieldset>
<!-- <legend>Create User</legend> -->
{{> afQuickField name="username" placeholder="schemaLabel" label=false}}
{{> afQuickField name="password" placeholder="schemaLabel" type="password" label=false}}
<div>
<button type="submit" class="btn btn-primary">Login</button>
</div>
</fieldset>
{{/autoForm}}
client/lib/helpers.js
AutoForm.hooks({
login_form: {
onSubmit: function (insert_doc, update_doc, current_doc) {
Meteor.call("is_user_enabled", insert_doc, function(error, result) {
if (result) {
// Try to log user in via Meteor.loginWithPassword()
}
});
}
}
});
server/lib/methods.js
Meteor.methods({
is_user_enabled : function(doc) {
// Used by the login form. Returns true if user exists and account is enabled.
check(doc, schemas.login);
var user = Meteor.users.findOne({username: doc.username}, {fields: {status: 1}});
if (user.status === "enabled") {
return true;
}
}
});
Final Solution:
client/lib/helpers.js
AutoForm.hooks({
login_form: {
onSubmit: function (insert_doc, update_doc, current_doc) {
Meteor.loginWithPassword(insert_doc.username, insert_doc.password, function(error) {
// Called with no arguments on success
// or with a single Error argument on failure.
if (error) {
FlashMessages.sendError(error);
this.done();
} else {
// Successful login. Redirect to /.
this.done();
Router.go('/');
}
});
return false; // Prevent browser submit event.
},
}
server/lib/permissions.js
Accounts.validateLoginAttempt(function (info) {
if (info.user && info.user.status === "enabled") {
return true;
} else {
throw new Meteor.Error("Invalid credentials.");
}
});
More info about [Accounts.validateLoginAttempt][1]
You can't prevent the client from calling a server method. Your checks for is_user_enabled and is_admin need to happen inside your server methods as well as on the client. You can of course have private functions inside your methods.js file that only methods on the server can access. For more tips see http://0rocketscience.blogspot.com/2015/07/meteor-security-no-1-meteorcall.html
Yes you can prevent Meteor methods from being executed from the client side. this.connection will only be set inside a method when it is called from the client. When called from the server it will be null. The enables you to do something like this:
serverOnlyMethod: function () {
if(this.connection) throw(new Meteor.Error(403, 'Forbidden.'));
}

How do I use the current user's username as a Router parameter in iron:router

I have a 'profile' template where I will display user related stuffs. So I wanna make a route for the template, but in the 'path' I want to dynamically insert the current user's username. Just the way we dynamically change the url with regard to post's id and everything.
Here's the router code block as of now.
Router.map(function() {
this.route('profile', {
path: '/profile', //here instead of 'profile' I wanna dynamically insert the current user's username.
});
});
By the way, I was able to load the user related data's to the said template.
I tried loading the username(/username) to the route path in a trial and error way, but in vain. :(
I guess I'm not very good with Iron Router after all. Please help.
I too was struggling with this one for a while... then I came across this SO answer. In my case, I was doing everything right except for failing to pass the username along with the template pathFor link helper.
For some reason, when using :_id in iron router routes, there's no need to reference it in the pathFor helper. This was the source of my confusion, perhaps others' as well.
Here is sample code of using the username in a path for iron router:
router.js
this.route('/:username', {
name: "dashboard",
waitOn: function() {
return Meteor.subscribe("allUserData");
},
data: function() {
return Meteor.users.findOne();
}
});
publications.js
Meteor.publish("allUserData", function() {
if (this.userId) {
return Meteor.users.find(this.userId)
} else {
this.ready()
}
})
page.html
<a href="{{pathFor 'dashboard' username=username}}">
User Dashboard
</a>
Again, at least in my particular case, I was missing the above username=username.
Have you tried this?
this.route('profile', {
path: '/:username',
data: function() { return Meteor.user().username; }
});
Use router parameters:
Router.map(function() {
this.route('profile', {
path: '/:_username', //dynamic parameter username
data: function() {
//here you will get the username parameter
var username = this.params.username;
return {
user: Meteor.users.find({ username: username }) //you can use user object in template
};
}
});
});
Don't forget the waitOn property on routes. Most of the time it's just the timing that's off, creating a publication for this is the best way to get rid of that issue..
Server side, publications.js:
Meteor.publish('me', function() {
if(!this.userId) return false;
else return Meteor.users.find({_id: this.userId});
});
In one of your Router.map() routes:
this.route('me', {
template: 'profile',
notFoundTemplate: 'profile_not_found',
path: '/profile',
waitOn: function() {
return Meteor.subscribe("me");
},
data: function() {
return Meteor.user();
}
});
Don't forget these configuration bits as well:
// Router config.. pretty self explanatory
Router.configure({
layoutTemplate: 'main',
notFoundTemplate: 'not_found',
loadingTemplate: 'loading'
});
// handle the loading screen
Router.onBeforeAction('loading');
// make sure you define routes here that rely on data to throw back
// 404/not found equivalent pages. e.g. no search results found,
// or in this case profile not found
Router.onBeforeAction('dataNotFound', {only: ['profile']});
and you can use the profile template:
<template name="profile">
Current user Id: {{_id}}
</template>
<template name="profile_not_found">
Profile not found. Are you logged in?
</template>

Backbone router and Meteor user authentication

I'm new to Meteor, and I would like to use a Backbone router in my project:
AppRouter = Backbone.Router.extend
({
"routes":
{
"": "home",
// ...
},
"home": function()
{
if (Meteor.user())
{
// ...
}
else
{
// ...
}
}
});
The problem I have is that, when a user is already logged in and goes to the "home" page, Meteor.user() returns null and Meteor.loggingIn() returns true...So I guess I need to wait for the end of the authentication, but how can I do that (Meteor.loginWithPassword seems to be called automatically when loading the page)?
assuming meteor calls some kind of callback when login finishes, delay calling Backbone.history.start() until then.

Resources