Getting a limit response from Loopback, when no authentication is provided - loopbackjs

I can't find a way to do it in the docs, and I have looked into as well here on Stack Overflow. I want to show a user a limited view of my JSON response from the API, before they have logged in.
So, as an example, I have a e-book I want to sell online. I want them only to see a preview link (epubFile.notAuthoried) of the book when not logged in, and the full link (epubFile.authorized) of the book when logged in. Both links are represented in the same table.
[
{
"title": "string",
"subTitle": "string",
"isPublished": true,
"publicationDate": "2017-10-20T11:07:31.258Z",
"epubFile": {
"notAuthorized": "filename-noauth.epub"
"authorized": "filename-auth.epub"
}
"id": "string",
"createdOn": "2017-10-20T11:07:31.258Z",
"updatedOn": "2017-10-20T11:07:31.258Z"
}
]
Is it even possible to filter out fields from the API Endpoints in loopback?
Or do I need to build a new custom API Endpoint?

first you'll have to set the permissions on your find and findById methods to $everyone so that both authorized and unauthorized users can call them
{
"name": "eBook",
"base": "PersistedModel",
[...]
"acls": [
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property":["find", "findById]
]
}
Next, you'll have to hook into the remote methods and modify the response depending on if hte user is logged in or not
const previewProperites = ['title', 'subTitle', etc...]
Ebook.afterRemote('find', (ctx, ebooks, next) => {
// pseudo code
if(!ctx.options.accessToken){
// no user logged in, only keep preview properties
ebooks.forEach(book => {
// get the properties of the book
var eBookProperties = Object.keys(book.__data);
eBookProperties.forEach(bookProp =>{
if(!previewProperties.some(pProp => pProp === bookProp)){
// ebook property not in preview list, so remove it
delete book.__data[bookProp]; // .__data is where loopback keeps its actual data
}
});
});
}
next();
}

Related

Amplify I can't get the Sub and other attributes of a user registered with oauth

I need to get attributes of a user when they log in with oauth. Currently Hub.listen returns me some user data like this:
{
"Session": null,
"authenticationFlowType": "USER_SRP_AUTH",
"client": {
"endpoint": "https://cognito-idp.us-east-1.amazonaws.com/",
"fetchOptions": [Object]
},
"keyPrefix": "CognitoIdentityServiceProvider.vifuv747v73gj48v",
"pool": {
"advancedSecurityDataCollectionFlag": true,
"client": [Client],
"clientId": "vifuv747v73gj48v",
"storage": [Function MemoryStorage],
"userPoolId": "us-east-1_gkfke8e",
"wrapRefreshSessionCallback": [Function anonymous]
},
"signInUserSession": {
"accessToken": [CognitoAccessToken],
"clockDrift": 0,
"idToken": [CognitoIdToken],
"refreshToken": [CognitoRefreshToken]
},
"storage": [Function MemoryStorage],
"userDataKey": "CognitoIdentityServiceProvider.sdfj656fs5efsdf45es.Google_0000000000000000000000.userData",
"username": "Google_0000000000000000000000"
}
Also I can't access them with: currentAuthenticatedUser()
const { attributes } = await Auth.currentAuthenticatedUser();
//attributes === undefined
When a user registers I have to perform certain tasks in the database, etc. Therefore I would like to know how to identify if the login by oauth created a new account to perform the necessary tasks. Or, failing that, to be able to access the sub for me to check if that user already existed and perform the tasks in case it is a new user
Found out how to access attributes when using Oauth to login:
const user = await Auth.currentAuthenticatedUser();
const attributes = user.signInUserSession?.idToken?.payload;
I noticed this adds some extra properties but it does what I need.

How to get campaign ID from a real time facebook lead?

I'm using Facebooks Webhooks for lead generations. I successfully can fetch leadgen_id from the Facebooks callback.
So this is what Facebook returns for the leadgen field:
{
"object": "page",
"entry": [
{
"id": "0",
"time": 1583178814,
"changes": [
{
"field": "leadgen",
"value": {
"ad_id": "444444444",
"form_id": "444444444444",
"leadgen_id": "444444444444",
"created_time": 1583178812,
"page_id": "444444444444",
"adgroup_id": "44444444444"
}
}
]
}
]
}
Is it possible to somehow get campaign ID from these values?
I figure out how to get the campaign id from leadgenId. we can use the Facebook graph leadgen GET API
GET /v6.0/<leadgenid>?access_token=<ACCESS_TOKEN>&fields=campaign_id'
Host: graph.facebook.com
Hope it'll help someone in the future :)

Loopback - How to access custom method with related model?

First please apologize my english.
I'm trying to access a custom remote method of one of my model with a related model.
I've created 3 models in Loopback which are related this way:
User hasMany Shop & Shop belongTo User
Shop hasMany Credit & Credit belongTo Shop
User.nestRemoting("shops") for User to reach Credit through Shop, User being the main entry point for the API.
Credit can be bought by a User for a Shop to execute some actions which cost Credit.
I've also created a custom remote method for Credit: availableCredit(shopId) which sum the Credit being available (those bought by User for a Shopand those spent by a User for a Shop).
I've registered my custom method in credit.json and it went fine.
"methods": {
"availableCredits": {
"description": Returns the available credits for the shop",
"accessType": "READ",
"shared": true,
"http": {
"path": "/available",
"verb": "get",
"status": 200
},
"accepts": [
{
"arg": "shopId",
"type": "string",
"description": "shop id",
"required": true
}
],
"returns": {
"arg": "credits",
"type": "number"
}
}
}
Everything works BUT my custom method DOESN'T show on API Loopback Explorer under shops or user.
I do see and have access to all the build-in methods like (in user):
GET /users/{id}/shops/{nk}/credits
Or my custom method in credit:
GET /credits/available
but I DON'T have
GET /users/{id}/shops/{nk}/credits/available
nor
GET /shops/{id}/credits/available
Does anyone knows if it's possible and has any idea of how it works ?
Thanks in advance.

Include related model in an hasOne relation

I have two models: Account and Customer both having an email address.
An Account can exist without a Customer and a Customer can exist without an Account.
However, an Account should return the related Customer record if this exists.
I was thinking about doing this by creating a hasOne relation on the Account using the unique identifier available in both records (the email address) as foreignKey.
Unfortunately this is not working.
These are my models:
Account
...
"properties": {
"username": {
"type": [
"string"
]
},
"email": {
"type": "string"
}
},
"validations": [],
"relations": {
"customer": {
"type": "hasOne",
"model": "Customer",
"foreignKey": "email"
}
}
...
Customer
...
"properties": {
"name": {
"type": [
"string"
]
},
"email": {
"type": "string"
}
},
"validations": [],
"relations": {}
...
By calling /api/account?filter={"include": ["customer"]} I don't get any additional information.
I don't understand if the problem is the foreignKey or the relation.
You could use an afterRemote hook to do the marshaling just before returning the requested instance.
However this won't be automatic, i.e. you still need to provide some sort of id to link the two instances together. In your case, if the email is such an id, then you would just search for a Customer instance with the same email as the Account instance.
The advantage is that you don't need to provide any extra filters or anything else to your query.
e.g.
Account.afterRemote('find', function(ctx, modelInstance, next) {
// Here you can check if the Account instance has a Customer instance
// via a regular find or findById, and if you do find the related instance
// you can add the data to ctx.result, which is the object that will be returned.
Customer.find({where:{email: modelInstance.email}}, addCustomerDetails);
function addCustomerDetails(err, linkedCustomer) {
// Add the Customer to the Account instance here
ctx.result.customer = linkedCustomer;
next();
}
});
And of course, you can do the same in the Customer afterRemote hook, but instead searching for the linked Account instance email.
Your models are defined well.
Be sure you have customer instance with existed email in db.
And the correct for of rest api calls is : /api/account?filter[include]=customer
UPDATE
Loopback overwrite the type of email because of the relation. hasOne relation should be setup over id foreign key not any other fields.
So if you want to fix the problem, you need to add below to properties section of account definition :
"id": false,
"email": {
"type": "string",
"id": true
}
The foreignKey field is just an alias for the relation. Having an email property and setting email as foreignKey does not create any sort of link between the two.
Then, it's simply a matter of using the REST API to instanciate the models, setup the relation and fetch the data
create an account
POST api/accounts/
{
"email": "account#bar.com"
}
create a related customer
POST api/accounts/1/foreignKey
{
"email": "customer#bar.com"
}
Fetch the accound and include the related customer
GET api/accounts/1?filter[include]=foreignKey

How to get the return data of a save with Ember Data?

I'm using Ember.js with ember-data to make a GUI for a self made API.
I've been following this tutorial to handle authentication but I want to use ember-data instead of custom jQuery requests.
One thing I have to do is to call the API to create a new session, by sending email and password, and the API sends me back an API Key object.
Here is my LoginController handling the loginUser action (it's CoffeeScript) :
App.LoginController = Ember.ObjectController.extend
actions:
loginUser: ->
session = #store.createRecord 'session',
email: #get 'email'
password: #get 'password'
session.save()
Here is the result I get when creating a session:
{
"users": [
{
"id": "525fa0286c696c0b14040000",
"email": "john.doe#mydomain.com",
"first_name": "John",
"surname": "Doe"
}
],
"api_key": {
"id": "526e464c6c696c07d2000000",
"type": "session",
"key": "6b824d6a-a065-4b6f-bb28-5c19389760f8",
"expires_at": "2013-10-28T11:41:08+00:00",
"user_id": "525fa0286c696c0b14040000"
}
}
I have Session, ApiKey and User models. I can create the session, but the thing I don't understand is how to access the return value of the save() method.
I know that my ApiKey and User are loaded somewhere because I get an error after save() if their respective Ember model don't exist but I don't know how to access them.
I've tried to use save() callbacks like then() or didCreate event but there's a lack of documentation about arguments passed to these callbacks and how to use them.
Ember.js 1.1.2
Ember Data 1.0.0.beta.3
EDIT:
I've tried to create an actuel Session model on my API, resulting in this JSON output:
{
"api_keys": [
{
"id": "526f69526c696c07d2110000",
"type": "session",
"key": "4c26af37-2b21-49c2-aef5-5850a396da0b",
"expires_at": "2013-10-29T08:22:50+00:00",
"user_id": "525fa0286c696c0b14040000"
}
],
"users": [
{
"id": "525fa0286c696c0b14040000",
"email": "john.doe#coreye.fr",
"first_name": "John",
"surname": "Doe"
}
],
"session": {
"id": "526f6e666c696c18c0010000",
"api_key_id": "526f69526c696c07d2110000"
}
}
(note the root element is now session)
It doesn't work better because now my save action leads to the following error (not in the console but then points to error callback):
Object function () { [...] } has no method 'eachTransformedAttribute'
I get this error, the relation between Session and ApiKey being declared in Ember Data models or not...
Your second example JSON looks better: since you are saving a Session, I would expect a session node in the response and other models to be side loaded. You can access the saved session after it's saved by using a promise callback:
session.save().then (savedSession) =>
console.log savedSession.api_key.key
Since you have _id relationship keys in your JSON, I assume you are using the ActiveModel adapter and its default serializer:
App.ApplicationAdapter = DS.ActiveModelAdapter.extend()