loopback api returning error AUTHORIZATION_REQUIRED - node.js

I am having two models : user and car
user acl :
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "login"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
car acl :
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
I am able to add car using api /cars but unable to get list of cars using api /cars , during get it is returning AUTHORIZATION_REQUIRED error code.
note : i am logged in as a user then i am trying to get list of cars owned by user.

It's possible that you're missing to add the model relation in agreement with Loopback documentation:
To qualify a $owner, the target model needs to have a belongsTo relation
to the User model (or a model extends from User) and property matching
the foreign key of the target model instance. The check for $owner is
only performed for a remote method that has ‘:id’ on the path, for
example, GET /api/users/:id.
Also I believe it would be a good idea change the default setting aclErrorStatus from 401 to 403. This way it would be easier to understand if it's an issue with authentication or authorization.

Related

Loopback v3 add column to Table -> "Unknown column 'column_name' in 'field list'"

I have a webshop based on React with a Loopback rest api. Now i want to add new columns to the client Table. If i add the object as property option to the client.js the error shows "Unknown column 'gender_delivery' in 'field list'". See the image below. The comments are not included in the saved code.
{
"name": "client",
"plural": "clients",
"base": "User",
"idInjection": true,
"options": {
"allowEternalTokens": true,
"validateUpsert": true
},
"ttl": 43200,
"mixins": {
"DisableAllMethods": {
"hide": [
"prototype.__create__orders",
"prototype.__delete__orders",
"prototype.__destroyById__orders",
"prototype.__updateById__orders",
"prototype.__count__cart",
"prototype.__create__cart",
"prototype.__destroy__cart",
"prototype.__update__cart",
"prototype.__destroyById__cart",
"prototype.__findById__cart",
"prototype.__addArticle__cart",
"prototype.__updateById___cart",
"prototype.__addArticles__cart",
"reset"
]
},
"TimeStamp": {
"createdAt": "created_at",
"updatedAt": "updated_at",
"required": false,
"validateUpsert": true,
"silenceWarnings": false
}
},
"restrictResetPasswordTokenScope": true,
"emailVerificationRequired": true,
"hidden": [
"password",
"verificationToken",
"securityQuestion",
"securityQuestion",
"verificationHash"
],
"properties": {
"id": {
"type": "number",
"id": true,
"generated": true,
"mysql": {
"columnName": "id",
"dataType": "int",
"dataLength": null,
"dataPrecision": 11,
"dataScale": 0,
"nullable": "N"
}
},
"oid": {
"type": "number"
},
"username": {
"type": "string",
"required": true
},
"password": {
"type": "string",
"required": true
},
"disable": {
"type": "boolean",
"required": true,
"default": false
},
"name": {
"type": "string"
},
"first_name": {
"type": "string",
"required": true
},
"middle_name": {
"type": "string"
},
"last_name": {
"type": "string",
"required": true
},
"street": {
"type": "string",
"required": true
},
"city": {
"type": "string",
"required": true
},
"telephone": {
"type": "string"
},
"fax": {
"type": "string"
},
"email": {
"type": "string",
"required": true
},
"deleted": {
"type": "boolean",
"required": true,
"default": false
},
"title": {
"type": "string"
},
"zip": {
"type": "string",
"required": true
},
"country": {
"type": "string",
"required": true
},
"company": {
"type": "string"
},
"lastlogin": {
"type": "number"
},
"is_online": {
"type": "boolean",
"required": true,
"default": false
},
"password_orig": {
"type": "string"
},
"gender": {
"type": "number"
},
"telephone2": {
"type": "string"
},
"iscompany": {
"type": "number"
},
"companyid": {
"type": "string"
},
"houseno": {
"type": "string"
},
"addressadditional": {
"type": "string"
},
"tax": {
"type": "boolean",
"required": true,
"default": true
},
"tax_type": {
"type": "number"
},
"securityQuestion": {
"type": "string",
"required": true
},
"securityAnswer": {
"type": "string",
"required": true
},
"verificationHash": {
"type": "string"
},
"showTutorial": {
"type": "boolean",
"default": true
},
// NEW COLUMNS
"gender_delivery": {
"type": "number"
},
"first_name_delivery": {
"type": "string",
"required": true
},
"last_name_delivery": {
"type": "string",
"required": true
},
"addressadditional_delivery": {
"type": "string"
},
"street_delivery": {
"type": "string",
"required": true
},
"houseno_delivery": {
"type": "string"
},
"zip_delivery": {
"type": "string",
"required": true
},
"city_delivery": {
"type": "string",
"required": true
},
"country_delivery": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"cart": {
"type": "hasOne",
"model": "cart",
"foreignKey": "clientId",
"primaryKey": "id"
},
"addresses": {
"type": "hasMany",
"model": "address",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
},
"orders": {
"type": "hasMany",
"model": "order",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
},
"projects": {
"type": "hasMany",
"model": "project",
"foreignKey": "clientId",
"options": {
"nestRemoting": true
}
}
},
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "create"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "deleteById"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "login"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "logout"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "findById"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "patchAttributes"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "replaceById"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "verify"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "verifyAccount"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "clearPassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "changePasswordPublic"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "confirm"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "resetPassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "changePassword"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "setPassword"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "deleteById"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "exists"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "findById"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "upsert"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "updateAll"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateClientAttributes"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__accessTokens"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__addArticle__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById___cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__addArticles__cart"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__addresses"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__createPdf__orders"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__count__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__create__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__delete__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__findById__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__get__projects"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__projects"
}
],
"methods": {}
}
The weird thing is that whenever i do the same with another Table it works perfectly fine. See example generas.
{
"name": "genera",
"plural": "generas",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"mixins": {
"IgnoreDeleted": true,
"RowCounter": true,
"TimeStamp": {
"createdAt": "created_at",
"updatedAt": "updated_at",
"required": false,
"validateUpsert": true,
"silenceWarnings": false
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"generated": true,
"mysql": {
"columnName": "id",
"dataType": "int",
"dataLength": null,
"dataPrecision": 11,
"dataScale": 0,
"nullable": "N"
}
},
// SEE EXAMPLE TEST
"TEST": {
"type": "number"
},
"oid": {
"type": "number",
"required": false
},
"lang": {
"type": "number",
"required": true
},
"name1": {
"type": "string",
"required": true
},
"name2": {
"type": "string",
"required": true
},
"deleted": {
"type": "boolean",
"required": true,
"default": false
},
"hidden": {
"type": "boolean",
"required": true,
"default": false
},
"sortscount": {
"type": "number",
"required": true
}
},
"validations": [],
"relations": {
"categories": {
"type": "hasMany",
"model": "genera_category",
"foreignKey": "generaId",
"through": "generas_categories_mm",
"options": {
"nestRemoting": true
}
},
"arts": {
"type": "hasMany",
"model": "art",
"foreignKey": "generaId",
"options": {
"nestRemoting": true
}
},
"sorts": {
"type": "hasMany",
"model": "sort",
"foreignKey": "generaId",
"options": {
"nestRemoting": true
}
}
},
"methods": {}
}
In Addition i've tried runing yarn update tables. This command doesn't run either. It says. Cannot read 'connector' of undefined.
Here is the code inside my drop-tables.js
/* jshint esversion: 6 */
/* jshint node: true */
'use strict';
const server = require('../server/server');
const ds = server.dataSources.mySqlIds;
const models = require('../server/model-config.json');
const tables = [];
const keys = Object.keys(models);
keys.forEach(key => {
const model = models[key];
if (key[0] !== '_' && model.dataSource ===
'mySqlIds') {
tables.push(key);
}
});
const dropTablePromises = [];
console.log(tables);
tables.map(table => {
const dropTable = new Promise(function(resolve,
reject) {
ds.connector.dropTable(
table, function(err, data) {
if (err) {
reject(err);
} else {
console.log(`\tDrop table "${table}".`);
resolve({name: table, data: data});
}
});
});
dropTablePromises.push(dropTable);
});
console.log('1. DROP TABLES');
Promise.all(dropTablePromises)
.catch(err => console.log(err))
.then(() => ds.disconnect());
The result of server.dataSources is an empty object = {}
that explains why it says 'ds' is undefined. (It doesn't work with datasources either).
My goal is just to extend the clients table by a few properties.
Thank you!

Loopback $owner doesn't works for findById

I would like to get some informations about my user with loopback.
For that I created a "user" model related with "accessToken" model until now a POST on /user, a POST on /user/login and a POST on /user/logout is working.
I added on /common/models/user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "accessToken",
"foreignKey": "userId"
}
},
"acls": [
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "logout"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "findById"
}
],
"methods": {}
}
And when I do a GET on /user/{id} I got :
{
"error": {
"statusCode": 401,
"name": "Error",
"message": "Autorisation requise",
"code": "AUTHORIZATION_REQUIRED",
"stack": "Error: Autorisation requise\n at..."
}
}
I guess I didn't understand acl/relation very well
This could be because you are only allowing $owner to findById:
To qualify a $owner, the target model needs to have a belongsTo relation to the User model (or a model that extends User) and property matching the foreign key of the target model instance. The check for $owner is performed only for a remote method that has ‘:id’ on the path, for example, GET /api/users/:id.
Make sure the accessToken you are providing is the owner of the id of the user you are looking for.
If you are not sure, try to replace:
"principalId": "$owner" with "principalId": "$authenticated", then you'll know if that's your problem.

loopback-component-storage acl 401 error

The container Model looks like this:
{
"name": "container",
"base": "Model",
...
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"accessType": "*"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"accessType": "WRITE"
}
]
}
When I post an image to it it returns a 401 error. I'm sure that I'm logged in as an 'admin' user because I make two requests with the same access token and the same acl in the json model. For the first (not container) it works, but for the containers one not.
Is there a problem with the container ACL?
-----Edit-----
Starting with DEBUG=loopback:security:acl it returns:
---ACL---
model container
property *
principalType ROLE
principalId $everyone
accessType *
permission DENY
with score: 7495
---ACL---
model container
property *
principalType ROLE
principalId admin
accessType WRITE
permission ALLOW
with score: -1
"property": "*" is default in acl.
For the first request (not container) it returns the same but admin-write-allow has o higher score than $everyone-*-deny.
Setting "score": -2 doesn't work.
You are not defining a property (a method basically), so I guess the ACl resolution gives higher weight to your first then second.
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"accessType": "*"
},
{
"principalType": "ROLE",
"principalId": "admin",
"property": "*", // Add this line
"permission": "ALLOW",
"accessType": "WRITE"
}
]
But then, ACL can be tough to figure out sometimes. I would recommend using debug string to see exactly what the ACL system has resolved:
On windows:
set DEBUG=loopback:security:acl && node .
EDIT:
The issue was actually coming from the accessType (requested endpoint was EXECUTE instead of WRITE), thus ACL not resolved as expected.

Strongloop - HasAndBelongsToMany always returns 401

I'm trying to execute this request:
PUT /api/cars/564d8e792583afef310affe3/categories/rel/suv-idcat
This works fine if I'm logged in as administrator, but if I'm logged in as another role, I get a 401 response.
My Car model has the following ACLs:
...
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__create__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__updateById__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__destroyById__categories"
}
...
Now, if I add this same ACL rules for a specific role:
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__create__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__updateById__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__destroyById__categories"
}
...
If I tap the endpoint logged in as a StoreAdmin user, then I get a 401 error response.
P.S. I've already taken a look at this: https://docs.strongloop.com/display/public/LB/Accessing+related+models, but there is no "hasAndBelongsTo" relation
I'll answer myself. It turns out that for the hasManyAndBelongsTo relation, the method names are not the same as the ones stated in the documentation for a hasMany relation. In fact, it is not even documented.
By running the app in debug mode: DEBUG=loopback:security:* I found out that the real method name was __link__categories.

Loopback - $owner role with a "hasMany" relation

I've been reading the loopback docs about roles. They state the following:
To qualify a $owner, the target model needs to have a belongsTo
relation to the User model (or a model extends from User) and property
matching the foreign key of the target model instance. The check for
$owner is only performed for a remote method that has ':id' on the
path, for example, GET /api/users/:id.
However, what happens when I have a "hasMany" relation and want to perform an action on some object like this:
PUT myusers/123/news/456
This would be my user.json:
{
"name": "MyUser",
"plural": "myusers",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"news": {
"type": "hasMany",
"model": "News",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}
Based on this, this and this. I've changed the MyUser entity to Writer entity, because I like it.
As the Writer entity has many News, the News relation and ACL should be something like this (news.json).
"relations": {
"writer": {
"type":"belongsTo",
"model":"Writer",
"foreignKey":"writer_id"
}
},
"acls": [
{ // Nobody has access to nothing
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{ // But everyone can read everything
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{ // And authenticated users can create news
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{ // And the owner of a news can update it
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
And the Writer entity has the same ACL rules but this relations (writer.json)
"relations": {
"news": {
"type": "hasMany",
"model": "News",
"foreignKey": "writer_id"
}
}
What really happens here is that, when you create a Writer, you must specify email and password becouse he inherited from User model. So if you want to perform the
PUT writers/123/news/456
You must be logged has a Writer which can be done in this endpoint: /api/writers/login (with email+password). This endpoint is going to give you the Writer token and then you'll be able to perform the update on the News if you has the $owner token on your header, url or form.
On the other hand, you can also get the user who is doing the HTTP Request and put that user has the news owner with a hook.
Hope it helps. Regards.

Resources