I'm currently writing an administrative interface using the Loopback Angular SDK. After having dug through the documentation and code, I'm still no wiser as to how to include the user's roles in the response. It's causing me real headaches on the frontend because I'm not yet experienced enough with Angular to figure out how to enforce a role check on each of my states (I'm using UI-Router).
client: /auth.js
// Log the user in
$scope.doAuth = function() {
$scope.hasError = false;
$scope.busy = true;
$scope.loginResult = User.login({include: 'roles'}, $scope.credentials,
function wasSuccessfulAuth(authResponse) {
$scope.busy = true;
$rootScope.isAuthenticated = true;
$rootScope.user = authResponse.user;
$location.path('dashboard');
},
function wasFailedAuth(authResponse) {
$timeout(function() {
$scope.hasError = true;
$scope.authError = authResponse.data.error.message || 'Unknown error';
$scope.busy = false;
}, 1000);
}
)
}
server: /common/models/user.json
{
"name": "user",
"plural": "Users",
"base": "User",
"properties": {
},
"relations": {
"roles": {
"type": "belongsTo",
"model": "RoleMapping",
"foreignKey": "principalId"
}
},
"acls": [],
"methods": []
}
So this works in the API explorer, I have the routes I'd expect with an object that has a relation, but I can't seem to get any further than that... All that gets returned is the standard user login stuff (id, accessToken, email, etc) The docs seem to run cold when I get this far but I'd have thought this would have been a common use case?
This is a bit of a showstopper for me.
It's actually surprisingly easy to solve this problem using LoopBack: this is where "model scopes" come in very handy -- including the default scope which I find extremely useful for this type of situation.
First, a brief explanation of model scopes:
A model scope is like a saved query or "view", that allows you to specify a built-in filter for any query for that scope. For example, if you set the default scope to a valid filter, every single query (of any kind) against your model will have this filter applied!
This can get you in a heap of trouble, but there's one use-case that's pretty safe (all other things equal) and actually addresses your question perfectly: when I said a scope lets you give a valid filter, it turns out filters aren't just where clauses, but also include, limit, etc.
So to solve your problem, you simply need a default scope on your User model that includes whatever you need to include. For your example:
Simply add a scope object to your common/models/user.json:
{
"name": "user",
"plural": "Users",
"base": "User",
"scope": {
"include": [
"roles"
]
},
"properties": {
},
"relations": {
"roles": {
"type": "belongsTo",
"model": "RoleMapping",
"foreignKey": "principalId"
}
},
"acls": [],
"methods": []
}
By adding a default scope with an "include", LoopBack will automatically embed the object(s) of the related model based on the named relation (just like if you added it in your query -- which as discussed, is not possible, or at least not easy, in this case).
One caveat: since Role and RoleMapping are built-in models and are surely marked as non-public, I am not actually sure whether you can include them directly (but for a different reason than above). I haven't worked enough with ACLs (yet), but presumably there's more complexity around access controls especially in related models.
So, whereas my example code above explain the mechanics of doing the include here, the policy concern may slow you down (I'd be curious to know if they do).
That said, however, I guess you could add a derived model (from RoleMapping) that you make public (just like you did for user), and use it instead everywhere (including in the relation to/from user) -- hopefully that's clear (let me know if not).
In conclusion: If you add a default scope to your derived user model, to do the include for you, the AngularJS service wrapper (built by lb-ng) will be none the wiser (the include all happens on the backend):
$scope.loginResult = User.login($scope.credentials,
function wasSuccessfulAuth(authResponse) {
console.log('Related models are here: ', authResponse.roles,
authResponse.user);
...
In other words, the resulting model will contain an array, .roles[], containing the related roles to this user (based on your relation definition), and .user with the entire user model right there!
This latter point is unclear but I'm confident in that, because I did exactly the above but with a different related model that I know works. And to my surprise, user was included as well, since that's explicitly requested by the LoopBack $resource wrapper (the one created by lb-ng). So, in fact, you don't have do a separate query to get the user -- it's already there! There's no API that I see when using the $service wrapper, to get at that built-in include and change it; I guess that's what was posted in a comment above.
Hope this is helpful.
Steve
User.login returns AccessToken instance. To get user role, you should make separate request to fetch user, including role.
Related
I created a Json Server Database like this:
"Time":
[
{
"id":1,
"name":
[
{
"id":1,
"checkin":
[
{
"id":1,
"date":"123",
"time":"123"
},
{
"id":2,
"date":"123",
"time":"123"
}
]
},
{
"id":2,
"checkout":
[
{
"id":1,
"date":"123",
"time":"123"
}
]
}
]
}
]
I don't want to get the entire Database and go through it. I just want to tell the Database where exactly my Object is and have it returned.
How would I call the call for example the first Check-in Object?
I use the Angular HttpClient like this:
this.http.get(endpoint, JSON.stringify(time), this.httpOptions))
So I need the Exact Endpoint in a format like: endpoint/id/id or similar
I imagined it like this: endpoint/time/1/1
With output:
[
{
"id":1,
"date":"123",
"time":"123"
}
]
If this is not possible please tell me anyways.
PS: The question from this thread is essentially the same as mine. Also the JSON documentation doesn't real help either, it just says you need custom routes for multilayer JSON strings but not how to implement these routes.
I'm not sure if I understand correctly where you are returning the data from. If you meant json-server, just look at the documentation (here) and then you could use an endpoint like "/posts?Id=2"
However, if you mean your own API, which does not have an endpoint that returns one record, e.g. by its ID, the only convenient solution is to create a service that will map the result from the server and return the desired value.
You can do all this in one place, but for clearer code, I recommend dividing it into:
service that will download data from the server
service that will map the data on the basis of a given parameter
component that will display the data
Below is a working example on Stackblitz.
example
Note that in the app-component I pass the ID 32 to the method from the mapping service as the parameter. The mapping service then calls a method that sends the request for all the data.
The important thing is that all data is returned to the application, not just one record. If the API you are using does not provide such an endpoint, it is not possible to return only one record.
Apparently a request like I wanted to call is still not possible. The only way to come close is to fake it with custom Routes and flattening the JSON structure like in this old thread.
I am writing an IdentityServer4 implementation and using the Quickstart project described here.
When you define an ApiResource (using InMemory classes for now) it looks like IdentityServer creates a Scope with the same name as the resource. For example
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api", "My API")
};
}
will create a Scope called "api" (this is done in the ApiResource constructor). If I add "api" as an allowed Scope on my Client object (using InMemoryClients for a proof of concept) and request this api Scope in the scope query string parameter in my auth request from my JavaScript client I get an invalid_scope error message.
I found by following this documentation you can add Scopes to the ApiResource through the Scopes property like so
new ApiResource
{
Name = "api",
DisplayName = "Custom API",
Scopes = new List<Scope>
{
new Scope("api.read"),
new Scope("api.write")
}
}
So now if I instead define my ApiResource like this and request the Scopes api.read and api.write (and add them to the AllowedScopes property on the Client Object) then everything works fine EXCEPT the consent page which shows duplicate Scopes. It shows api.read 2 times and api.write 2 times. See the consent screen here
The Client configuration is as follows:
new Client
{
ClientId = "client.implicit",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:3000/health-check" },
PostLogoutRedirectUris = { "http://localhost:3000" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"customApi.read", "customApi.write"
}
}
Why is this happening? Am I doing something obviously wrong?
Update:
Here a portion of the discovery document that shows the Scopes are only listed once...
It looks like the problem is with the Quickstart UI... or with the Scope.cs class depending on how you look at it. Specifically, in the method and line shown in the class ConsentService.cs
The following code
vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
is not filtering out the duplicates. That is, even if two Scopes have the same name they are not considered equal. So if GetHashCode and Equals were overridden in Scope.cs (which is in IdentityServer4 - not the Quickstart) then it would solve this problem. In that case SelectMany would return a unique set. This is because the ApiResources property is implemented as a HashSet. Alternatively, you could write your own logic to make this return a unique set of Scopes. This is how I solved the problem. I wrote something very similar to Jon Skeet's answer in this post that filtered out the duplicate Scopes.
The problem lies within IdentityService4 code in the implementation of InMemoryResourcesStore.FindApiResourcesByScopeAsync and was fixed with this commit. You can use the dev branch where it's included since June 22th 2017, but it was never released in any of the NuGET packages targeting .NET Standard 1.4, which is very annoying.
I created an issue and requested it to get patched:
https://github.com/IdentityServer/IdentityServer4/issues/1470
For fixing the view, i added the line marked with Todo to ConsentService.cs
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
{
// TODO: Hotfix to cleanup scope duplication:
resources.ApiResources = resources.ApiResources.DistinctBy(p => p.Name).ToList();
return CreateConsentViewModel(model, returnUrl, request, client, resources);
}
This solves the display problem, but the scope will still be included multiple times in the access token which makes it bigger since it squares the scope count for that API. I had 3 scopes, so each one was included 3 times, adding 6 unneeded scope copies. But at least it's usable until it get's fixed.
There was a bug that was just fixed in 1.5 that addresses this: https://github.com/IdentityServer/IdentityServer4/pull/1030. Please upgrade and see if that fixes the issue for you. Thanks.
I'm experimenting with a Conversation where I would like to modify the output in a couple of different ways:
different output for speech or text
different output depending on the tone of the conversation
It looks like I can add extra output details which make it through to the client ok. For example, adding speech alongside text...
{
"output": {
"speech": {
"Hi. Please see my website for details."
},
"link": "http://www.example.com",
"text": {
"Hi. Please see http://www.example.com for details."
}
}
}
For the tone, I wondered about making up a custom selection policy, unfortunately it seems to treat it the same as a random selection policy. For example...
{
"output": {
"text": {
"values": [
"Hello. Please see http://www.example.com for more details.",
"Hi. Please see http://www.example.com for details."
]
},
"append": false,
"selection_policy": "tone"
}
}
I could just add a separate tone-sensitive object to output though so that's not a big problem.
Would there be any issues adding things to output in this way?
You can definitely use the output field to specify custom variables you want your client app to see with the benefit that these variables will not persist across multiple dialog rounds (which they would if you would add them to the context field).
Now currently there is no "easy" way how to define your custom selection policy (apart from the random and sequential supported by the runtime right now) - but you could still return an array of possible answers to the client app with some attribute telling the client app which selection policy to use and you would implement this policy in the client app.
Say I have an API endpoint at /users and another one at /cars. Naturally, a GET request to either of them will get all users or cars available. Now, a GET /users/74/cars should return all cars belonging to user 74.
But my app has many models related to cars, not just users, so more endpoints exist like /shops/34/cars and /mechanics/12/cars. For simplicity, I want all PUT/PATCH requests to be made to the main /cars endpoint.
At the moment of performing the save, Restangular will by default do a PUT request to the endpoint through which the item was loaded. But that endpoint do not exist.
It also provides a nice Restangular.setParentless(['cars']) method that will discard the first part of the url. However, I don't want to do this globally, but specifically for a particular element.
The neatest would actually do it globally, but restrict it for a specific method, like: Restangular.setParentless(['cars'], ['PUT']).
Anything like that around? Or am I overcomplicating it?
So far I tried stuff I don't like:
delete car.parentResource;
I would recommend using self reference links. A self reference link is a link which stores the route which should be used for GET/PUT/DELETE etc. on the item, rather than the URL from which it was pulled.
Example, update the mileage on one of user id 74's cars:
First, configure Restangular to look for a self link property called 'self' on each object.
RestangularProvider.setRestangularFields({
selfLink: 'self'
});
Next, make your call to get the cars. I'll assume that you have already modified your API to return a property called 'self' on each object that has the URL to it's proper API endpoint.
GET /users/74/cars
[
{
"id": 12,
"model": "Camaro",
"make": "Chevrolet",
"year": 1969,
"color": "red",
"odometer": 67294,
"license": "ABC12345",
"self": "/cars/12"
},
{
"id": 14,
"model": "Gallardo",
"make": "Lamborghini",
"year": 2015,
"color": "black",
"odometer": 521,
"license": "XYZ34567",
"self": "/cars/14"
}
]
We want to add some miles to one of them, and then save it. The entire Restangular code would look like:
Restangular.one('users', 74).all('cars').getList().then(function(cars){
cars[1].odometer = 613;
cars[1].put();
});
The PUT will go to /cars/14 instead of /users/74/cars/14. This is very useful for applications like yours that relate models as a graph rather than a strict hierarchical tree.
WARNING: fairly long question
I am new to angular and I've gone through a few tutorials and examples such as the official tutorial on the angularjs website. The recommended way to get data into your app and share that data between controllers seems very clear. You create a service that is shared by all controllers which makes an asynchronous request to the server for the data in JSON format.
This is great in theory, and seems fine in the extremely simple examples that only show one controller, or when controllers don't share logic that depends on the shared data. Take the following example of a simple budget application based on yearly income and taxes:
Create the app with a dependency on ngResource:
var app = angular.module('budgetApp', ['ngResource']);
NetIncomeCtrl controller that handles income and tax items, and calculates net income:
app.controller('NetIncomeCtrl', function ($scope, BudgetData) {
var categoryTotal = function (category) {
var total = 0;
angular.forEach(category.transactions, function (transaction) {
total += transaction.amount;
});
return total;
};
$scope.model = BudgetData.get(function (model) {
$scope.totalIncome = categoryTotal(model.income);
$scope.totalTaxes = categoryTotal(model.taxes);
});
$scope.netIncome = function () {
return $scope.totalIncome - $scope.totalTaxes;
};
});
BudgetData service that uses $resource to retrieve the JSON data from the server:
app.factory('BudgetData', function ($resource) {
return $resource('data/budget.json');
});
budget.json contains the JSON data returned from the server:
{
"income": {
"transactions": [
{
"description": "Employment",
"amount": 45000
},
{
"description": "Investments",
"amount": 5000
}
]
},
"taxes": {
"transactions": [
{
"description": "State",
"amount": 5000
},
{
"description": "Federal",
"amount": 10000
}
]
},
}
Then on my screen I have two repeaters that show the income and tax items (which you can edit), and then the net income is calculated and displayed.
This works great, and is the standard approach I've seen used in tutorials. However, if I just add one more controller that depends on some of the same data and logic, it begins to unravel:
ExpensesCtrl controller for expenses which will in the end calculate the surplus (net income - expenses):
app.controller('ExpensesCtrl', function ($scope, BudgetData) {
var categoryTotal = function (category) {
var total = 0;
angular.forEach(category.transactions, function (transaction) {
total += transaction.amount;
});
return total;
};
$scope.model = BudgetData.get(function (model) {
$scope.totalIncome = categoryTotal(model.income);
$scope.totalTaxes = categoryTotal(model.taxes);
$scope.totalExpenses = categoryTotal(model.expenses);
});
$scope.netIncome = function () {
return $scope.totalIncome - $scope.totalTaxes;
};
$scope.surplus = function () {
return $scope.netIncome() - $scope.totalExpenses;
};
});
budget.json adds the expenses data:
"expenses": {
"transactions": [
{
"description": "Mortgage",
"amount": 12000
},
{
"description": "Car Payments",
"amount": 3600
}
]
}
Then on a separate part of the screen I have a section that uses this controller and uses a repeater to show the expense items, and then re-displays the net income, and finally shows the resulting surplus.
This example works, but there are several problems (questions) with it:
1) In this example I've managed to keep most of my controller's logic out of the callback function, but all controllers start out in a callback because everything depends on the model being loaded. I understand this is the nature of javascript, but angular is supposed to reduce the need for all of these callbacks, and this just doesn't seem clean. The only reason I was able to take some of the logic out of the callbacks here is because of the magic that angular does under the hood to substitute a fake object for the model until the model gets loaded. Since this 'magic' is not intuitive, it's hard to tell if your code will work as expected.
Is there a consistent way people deal with this? I really don't want some elaborate solution that makes this 101 intro app into something really complicated. Is there a simple and standard approach to restructuring this code somehow to avoid so many callbacks and make the code more intuitive?
2) I have a bunch of repeated logic. The categoryTotal and netIncome logic should only be in one place, and shared between controllers. These controllers are used in completely separate parts of the screen, so they can't use scope inheritance. Even when using scope inheritance there could be problems, because the child controller's scope can't depend on the parent scope's model being loaded even when its own model is loaded.
The categoryTotal logic is just a helper function that isn't tied directly to the model, so I don't know where you put generic helper functions in angular. As for the netIncome which does directly depend on the model and needs to be in the scope, it would be possible to add it to the service or the model. However, the service should only be concerned with retrieving the model from the server, and the model should just contain data, and be as anemic as possible. Also, it seems like this kind of logic belongs in a controller. Is there a standard way to deal with this?
3) Every time the service is called to fetch the data, it does an http request to the server, even though the data is the same every time. The model should be cached after the first request, so there is only one request made.
I realize I could handle this in the service. I could just store the model as a local variable and then check that variable to see if the data already exists before making the request. It just seems odd that none of the tutorials I read even mentioned this. Also, I'm looking for a standard 'angular' way of dealing with it.
Sorry for the incredibly long post. I would really like to keep this app at the 101 intro level, and not get into really complicated areas if possible. I'm also hoping there is a standard 'angular' way to deal with these problems that I have just not come across yet.
Thanks in advance!
This is how I do it. You create a service that handles data, and if it is changed it broadcasts messages to your controllers. It will get the initial data that can be gotten with BudgetData.data. If anyone changes the data
.service("BudgetData", function($http, $rootScope) {
var this_ = this, data;
$http.get('wikiArticles/categories', function(response) {
this_.set(response.data);
}
this.get = function() {
return data;
}
this.set = function(data_) {
data = data_;
$rootScope.$broadcast('event:data-change');
}
});
In your controller you just need to listen for the events, and it will update your scope variables accordingly. You can use this in as many controllers as you want.
$rootScope.$on('event:data-change', function() {
$scope.data = BudgetData.get();
}
$scope.update = function(d) {
BudgetData.set(d);
}