I have an Angular method that deletes a user from the users table, but the authorization is happening in Rails. If an unauthorized exception is thrown when deleting the user, Angular doesn't catch the exception, so it appears as if the request was successful.
From my research, this is dealt with using interceptors, but I haven't been able to get it to work. I have the following Angular method and interceptor.
Is this the correct way to do this? If not, what is? If yes, what am I doing wrong? Not really sure how an interceptor is supposed to work, and the docs haven't been very helpful.
service('UserTableService', [ '$http', ($http) ->
constructor: ->
destroyUserRow: (id, error) ->
$http.delete("/users/#{id}").success(() ->
# Do something
).error((data) -> error(data))
]).
factory('deleteUserInterceptor', ['$injector','$q', ($injector, $q) ->
(promise) ->
$http = $injector.get('$http')
promise.then null, (response) ->
if response.status == 401 || response.status == 403
$scope.$emit("Unauthorized")
$q.reject response
]).
Related
I need the result of an api call to another server for a react component: let's say for now I just need to display either true or false as text
I have the following component:
// some_random_thing.coffee
{ div, h5 } = DOM
actions = require '../../actions'
class MfaQRCode extends React.Component
#propTypes: {}
constructor: ->
super arguments...
#validationTimeout = null
shouldComponentUpdate: -> true
render:->
div {},
#renderStatus()
renderStatus: ->
h5 {className: 'column-header'},
"BOOLEAN::"
#props.resource.boolean_record
Which is a child component of something else;
Then to populate this boolean_record I have in my actions.coffee file
api = require('./api')
AddUserPopupActions = ReactUtils.actions.creators.popup
somethingExists: { asyncResult: true }
AddUserPopupActions.somethingExists.listen ->
#promise(api.somethingExists(arguments...))
In api.coffee
api = ReactUtils.api
class Api
somethingExists: ->
api.get "/servers/users/something/exists"
This get request is handled in a rails controller which I believe to be fine. My question is, how can I populate a variable so that I can use the result of the request which returns true or false from another server.
There is a method in a file named store.coffee where the resource component of props is populated I need to populate this with the result of the api request: have tried:
onSomethingExists: (response) ->
#resource.boolean_record = response.responseJSON.result
#_update()
But this method is not getting triggered at all it seems upon debugging. I'm very new to react and do not fully understand how it all works and given the fact coffeescript and rails are in the mix there is little information to be found on how to do this.
It looks like you are using refluxjs
If this is the case, your store should be listening to your actions.
onSomethingExists: (response) ->
This is called when the somethingExists method is called, but the arguments are what you pass to the somethingExists function.
You need to add two more functions in your store to handle successful / error result to your request:
onSomethingExistsCompleted: (response) ->
console.log "onSomethingExistsCompleted", response
onSomethingExistsFailed: (response) ->
console.log "onSomethingExistsCompleted", response
To get values from this into your react view, you will need to use setState in the store, and then it should arrive in the view on #state given that said view is connected to the store
I'm integrating a CRM with Facebook lead Ads using Zapier, and I can create a lead in Facebook and create it in the CRM without any issues.
After a successful post (i.e. successfully creating the lead), I'm curious what I should be returning, I would have thought
return Ok();
would have been enough. Instead, I get an error message saying:
- Got a non-object result, expected an object from create ()
What happened (You are seeing this because you are an admin):
Executing creates.ZapLead.operation.perform with bundle
Invalid API Response:
- Got a non-object result, expected an object from create ()
What should I be returning?
Code which makes the post is:
perform: (z, bundle) => {
const promise = z.request({
url: 'https://{{bundle.authData.subdomain}}.ngrok.io/api/zapier/create/lead/' + bundle.inputData.lead_type + '/' + bundle.inputData.core_customerTypeId,
method: 'POST',
body: JSON.stringify({
fields: bundle.inputData
}),
headers: {
'content-type': 'application/json'
}
});
// return promise.then((response) => JSON.parse(response.content));
return promise.then((response) => {
if (response.status != 200) {
throw new Error(`Unexpected status code ${response.status}`);
}
});
Any ideas?
Thanks,
David here, from the Zapier Platform team.
While your answer is technically correct, I wanted to add some context about the error and why your solution works.
Zapier expects a javascript object (basically anything valid and wrapped in {}) to come out of a create function. That's why JSON.parse(response.content) works, it's returning whatever the server said. Your code throws an error if it's not a 200, but doesn't return anything if it is a 200. Since undefined is not of type Object, the error you're seeing is thrown.
While {"Success":"Success","Attempt":null,"Id":null,"RequestId":null} is totally a valid response (it's an object!), it's more useful for the end-user to return data about the new lead that was created. That way, it can be used downstream for other actions.
Let me know if you've got any other questions!
As a side note, we're very open to how to make that error message more clear; it's one devs struggle with a lot.
I think I found it. Looks like I need to return this if successful:
{"Success":"Success","Attempt":null,"Id":null,"RequestId":null}
I have functions in my API code, some of which should only return requested results only to those requests that have a correct token. If the request is not authorized, then return a generic 401 response.
I have created a helper function is_authorised() which returns true/false.
Now, in a function that I want to make accessible only to authorized users, I check the result of the is_authorised() function and return respective response.
Here is an example:
get_players(SessionID, _Env, _Input) ->
case is_authorized(_Env) of
true ->
Response = [json_header(), players:select_all()];
false ->
Response = get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
I wonder if it's possible to make this checking look more elegant, like Python decorators used in Flask.
#app.route('/user')
#required_roles('admin', 'user')
def user_page(self):
return "You've got permission to access this page."
I know I can't use custom functions in guards, so I think it's not possible to make it as a guard.
Please advise.
Not as elegant as Python decorators, but you can use higher order functions to make this much cleaner. Create a function that accepts SessionID, Env, Input, and a callback function that should be executed when the user is authorized, and call that from all the functions that need to do the authorization check.
(Untested) code:
authorized(SessionID, Env, Input, Fun) ->
Response = case is_authorized(Env) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, fun() ->
[json_header(), players:select_all()]
end).
You can pass more arguments to authorized if you want to do more extensive checks. With a role_of/1 function that accepts Env and returns the role as an atom, you can allow access to certain users with something like this:
authorized(SessionID, Env, Input, Roles, Fun) ->
Response = case lists:member(role_of(Env), Roles) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, [admin, user], fun() ->
[json_header(), players:select_all()]
end).
You might want to look into cowboy or webmachine as they'd give you a framework over a state machine to handle REST requests. Implement the 'forbidden/2' callback to indicate unauthorized access.
On a side note: a 401 status code indicates a failure of authentication at the http layer when doing one of the well known http auth mechanisms like basic. See also https://en.wikipedia.org/wiki/Basic_access_authentication
What you are dealing with is a authorization failure and the correct associated http status code for that case would be 403 (as the forbidden callback from above would return).
I've got a AngularJS/Rails app and I want to test my AngularJS controller is posting to the backend server when creating a new record. (I'm using jasmine for my tests)
Here is my attempted test
describe "create", ->
beforeEach(inject ( ($controller, $rootScope, $location, $state, $httpBackend) ->
#redirect = spyOn($location, 'path')
#httpBackend.whenGET('/assets/layouts/default.html.erb').respond(200)
#httpBackend.whenGET('/assets/letters/index.html.erb').respond(200)
#httpBackend.whenPOST('/api/letters').respond(200)
$controller("LettersController", { $scope: #scope, $location: #location })
))
it "sends a post to the backend", ->
#httpBackend.expectPOST('/api/letters', {"letter":{},"_utf8":"☃"}).respond(200)
#scope.create()
Here s the code which I'm testing:
$scope.create = ->
Letter.save(
{}
,
letter:
subject: $scope.letter.subject
body: $scope.letter.body
# success
, (response) ->
$location.path "/letters"
# failure
, (response) ->
)
The code in question works correctly and the test passes. The problem is if I comment my Letter.save code out (which makes the post through AngularJS resources) then my test still passes.
How can I get my test to work properly?
My full test application is here: https://github.com/map7/angularjs_rails_example2
You need to verify that there are no outstanding requests at the end of your tests:
$httpBackend.verifyNoOutstandingRequest();
You also need to verify that there are no outstanding expectations with $httpBackend.verifyNoOutstandingExpectation
This should prevent you from getting false positives I believe.
Also, I'm not sure that your expectPOST should return true because you don't seem to be sending "_utf8":"☃", however I haven't looked at the full source code so I could be missing something.
I would try to trim down the example so that your create method calls a route and you expect that route to be called and work from there. You might try removing the whenPOST and replace the expectPOST with #httpBackend.expectPOST('/api/letters').respond(200)
I'm building an app which is architected as a Rails server app providing RESTful api's to the client. The Rails server uses RABL. The client is an Angular JS client performing standard $http calls (gets, puts, etc).
Occasionally my Rails server will produce an error (let's say validation error attached to the object) or even no error in which case I would want to display something to the user - either the errror e.g., "The record did not save because..." or "The record was updated successfully".
I'm trying to map out a pattern on both the Rails side and the Angular/client side to handle this.
As for Rails:
I can certainly pass back a node in each of my RABL files to contain error arrays
I can also return different RABL by checking in the controller before returning
Most suggest using http codes (which makes sense) as per here (although there doesn't seem to be a consistent usages of the codes for something like a validation error).
As for Angular:
I suppose I can write a response interceptor but not sure how that would fully get flushed out.
I guess I'm hoping that I don't have to reinvent the wheel here and someone can point me to a pattern that's currently used and suggested (and localized).
I went ahead and implemented what I thought needed to be done. Thanks for digger69 for some help with this.
On the Rails side, I went with using an http status code. As per here I agreed with using a 400 http status code for error validation.
In my controllers I now have something like the following:
def create
my_obj = MyObj.build_with_params(params)
if my_obj.save
respond_with(my_obj) # regular RABL response
else
respond_with_errors(my_obj.errors)
end
end
In my application_controller.rb I defined a common method respond_with_errors
# respond back to the client an http 400 status plus the errors array
def respond_with_errors(errors)
render :json => {:errors => errors}, :status => :bad_request
end
Note that the :bad_request symbol is already defined for Rails as per here
On the client side I needed to intercept http calls (not only for validation but for authentication failures too (and probably more). Here is an example of my code in Angular (thanks to this post for the help with that):
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) { // unauthorized - redirect to login again
window.location = "/";
} else if (status == 400) { // validation error display errors
alert(JSON.stringify(response.data.errors)); // here really we need to format this but just showing as alert.
} else {
// otherwise reject other status codes
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
I now can be consistent with my rails code and deal with success returns from http calls on the client. I'm sure I have some more to do, but I think this gives a localized solution.
Use an HTTP response interceptor. I am currently using that successfully in an application.
http://docs.angularjs.org/api/ng.$http
From the documentation:
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return function(promise) {
return promise.then(function(response) {
// do something on success
}, function(response) {
// do something on error
if (canRecover(response)) {
return responseOrNewPromise
}
return $q.reject(response);
});
}
});
$httpProvider.responseInterceptors.push('myHttpInterceptor');
In my case I created a feedback service, which displays either success or error messages globally. An other option would be to broadcast the responses on the rootscope.