In my project, we plan to implement a progressive disclosure (displaying some part of UI data if the user has permissions). Basically, we are using react + apollo client with hooks + graphqltag.
But the problem is no that how to hide some part of UI but how to split queries by permissions. So right now for pages, we create a single query containing many "subqueries" for different kinds of data. For example :
export const GET_DATA_X= gql`
query getDataX(
$applicationId: ID!
$dateFrom: String!
$dateTo: String!
$displayMode: String!
) {
applicationShipDates(
applicationId: $applicationId
dateFrom: $dateFrom
dateTo: $dateTo
displayMode: $displayMode
) {
periodStartDate
dates
}
graphStatistics(
applicationId: $applicationId
dateFrom: $dateFrom
dateTo: $dateTo
) {
totalVisits
totalConversions
conversionRate
}
}
`;
And right now each part of this query will be available if the user will have permission. On the backend side, it's already handled. We throw null/empty array and error. But IMO we shouldn't even ask for this part of the data. And that is the question. Do you have any suggestions on how to do this with an apollo client?
Right now I have two ideas on how to do that:
Split queries into single and make a few API calls if the user has permission, otherwise skip it
Write a custom function where I will pass as a prop array of objects, including query definition and query required permissions. I will filter this array by permission and from small query definitions like applicationShipDates or graphStatistics i will create a big query like getDataX which will includes few "subqueries"
Like #xadm mentioned directives will be the best solution.
Related
I was trying to debug a problem related to refunding Paypal orders (in a sandbox environment) using order IDs (which were stored previously). Every time I tried to perform a refund, the Paypal API would return an INVALID_RESOURCE_ID error, meaning that no such order existed. After much debugging, I have made a revelation with the initial process when I stored said order ID. The following method is how I am retrieving and storing said order id:
const onApprove = (data, actions) => {
// Redux method of saving checkout in backend with order ID via using data.orderID
dispatch(saveCheckout(data.orderID);
return actions.order.capture();
}
<PayPalButton
amount={totalPrice}
currency= "AUD"
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
options={{
clientId: "<placeholder>",
currency: "AUD"
}}
/>
I am using the recommended data.orderID from the docs and yet, upon inspecting the network tab, the following is shown:
{"id":"5RJ421191B663801G","intent":"CAPTURE","status":"COMPLETED","purchase_units":[{"reference_id":"default","amount":{"currency_code":"AUD","value":"24.00"},"payee":{"email_address":"sb-sg4zd7438633#business.example.com","merchant_id":"EJ7NSJGC6SRXQ"},"shipping":{"name":{"full_name":"John Doe"},"address":{"address_line_1":"1 Cheeseman Ave Brighton East","admin_area_2":"Melbourne","admin_area_1":"Victoria","postal_code":"3001","country_code":"AU"}},"payments":{"captures":[{"id":"7A2856455D561633D","status":"COMPLETED","amount":{"currency_code":"AUD","value":"24.00"},"final_capture":true,"seller_protection":{"status":"ELIGIBLE","dispute_categories":["ITEM_NOT_RECEIVED","UNAUTHORIZED_TRANSACTION"]},"create_time":"2021-10-11T00:40:58Z","update_time":"2021-10-11T00:40:58Z"}]}}],"payer":{"name":{"given_name":"John","surname":"Doe"},"email_address":"sb-432azn7439880#personal.example.com","payer_id":"KMEQSKCLCLUZ4","address":{"country_code":"AU"}},"create_time":"2021-10-11T00:40:48Z","update_time":"2021-10-11T00:40:58Z","links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/5RJ421191B663801G","rel":"self","method":"GET"}]}
The id saved by onApprove is 5RJ421191B663801G but there is another ID under captures and id which is 7A2856455D561633D. This is the actual order id I need to save in order to make the refund later on. However, I am struggling as to how I can retrieve this value as that id value seems to be only visible via the network. The objects returned via the onApprove and action.order.get() methods only return the first "false" id. Any advice would be greatly appreciated.
These are two separate types of IDs, the order ID (used only during buyer checkout approval), and the payment/transaction ID (which only exists after an order is captured, and is the one needed for any later refund or accounting purposes)
Since you are capturing on the client side with actions.order.capture(), this is where you would need to add a .then(function(data){ ... }) to do something with the capture data (particularly data.purchase_units[0].payments.captures[0].id). That is the id you would use for a refund.
In actual best practice, if anything important needs to be done with the capture id -- such as storing it in a database for reference -- you should not be creating and capturing orders on the client side, and instead calling a server-side integration where that database write will be performed.
Follow the Set up standard payments guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order', documented here. Both routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should store its resulting payment details in your database (particularly the aforementioned purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller.
Pair those 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
Or for react, use the official react-paypal-js
I am thinking should I audit user queries in HttpRequestInterceptor or DiagnosticEventListener for Hot Chocolate v11. The problem with latter is that if the audit failed to write to disk/db, the user will "get away" with the query.
Ideally if audit fail, no operation should proceed. Therefore in theory I should use HttpRequestInterceptor.
But How do I get IRequestContext from IRequestExecutor or IQueryRequestBuilder. I tried googling but documentation is limited.
Neither :)
The HttpRequestInterceptor is meant for enriching the GraphQL request with context data.
The DiagnosticEventListener, on the other hand, is meant for logging or other instrumentations.
If you want to write an audit log, you should instead go for a request middleware. A request middleware can be added like the following.
services
.AddGraphQLServer()
.AddQueryType<Query>()
.UseRequest(next => async context =>
{
})
.UseDefaultPipeline();
The tricky part here is to inspect the request at the right time. Instead of appending to the default pipeline, you can define your own pipeline like the following.
services
.AddGraphQLServer()
.AddQueryType<Query>()
.UseInstrumentations()
.UseExceptions()
.UseTimeout()
.UseDocumentCache()
.UseDocumentParser()
.UseDocumentValidation()
.UseRequest(next => async context =>
{
// write your audit log here and invoke next if the user is allowed to execute
if(isNotAllowed)
{
// if the user is not allowed to proceed create an error result.
context.Result = QueryResultBuilder.CreateError(
ErrorBuilder.New()
.SetMessage("Something is broken")
.SetCode("Some Error Code")
.Build())
}
else
{
await next(context);
}
})
.UseOperationCache()
.UseOperationResolver()
.UseOperationVariableCoercion()
.UseOperationExecution();
The pipeline is basically the default pipeline but adds your middleware right after the document validation. At this point, your GraphQL request is parsed and validated. This means that we know it is a valid GraphQL request that can be processed at this point. This also means that we can use the context.Document property that contains the parsed GraphQL request.
In order to serialize the document to a formatted string use context.Document.ToString(indented: true).
The good thing is that in the middleware, we are in an async context, meaning you can easily access a database and so on. In contrast to that, the DiagnosticEvents are sync and not meant to have a heavy workload.
The middleware can also be wrapped into a class instead of a delegate.
If you need more help, join us on slack.
Click on community support to join the slack channel:
https://github.com/ChilliCream/hotchocolate/issues/new/choose
So I have a database that store users. When someone log on my website, it stores in apollo cache a user as currentUser. And I only store his id.
So I made a query to get a user by passing his id :
query {
user(id: "id") {
id
username
avatar
}
}
But everytime I wanna get data for that user I need to make two query (the first one locally to get back his id from the cache and a second one to the server).
const GET_CURRENT_USER = gql`
query getCurrentUser {
currentUser #client
}
`;
const GET_USER_DATA = gql`
query getUser($id: String!) {
user(id: $id) {
id
username
avatar
}
}
`;
const currentUserData = useQuery(GET_CURRENT_USER);
const { currentUser } = currentUserData.data;
const { data, loading } = useQuery(GET_USER_DATA, {
variables: { id: currentUser.id },
fetchPolicy: "cache-and-network"
});
Is here a way that I can reduce that to only one query (the one to the server) ?
id value stored in the cache can be read using readQuery, you can store it in other global store/state, f.e. redux.
If you're using apollo cache as global store then using queries is a natural part of this process.
Using readQuery you can read the value without querying (but doing the same). One query 'saved' ;)
Deeper integration (additional query, local resolver) is not a good thing - creating unnecessary dependencies.
If you want to reuse this "unneccessary query" extract it to some module or create a custom hook (id read/used/saved once during initialization) - probably the best solution for this scenario.
Another solutions:
make login process providing user data - for some inspiration take a look at apollo-universal-starter-kit - but this is for initial data only (login/avatar changing during session??) - further user querying still needs an id parameter - it must be stored and read somewhere in the app.
make id optional parameter (for getUser query - if you can change backend) - if not provided then return data for current user (id read from session/token)
I'm actually creating my first app using meteor, in particular using angular 2. I've experience with Angular 1 and 2, so based on it. I've some points of concern...
Let's imagine this scenario...My data stored on MongoDb:
Collection: clients
{
name : "Happy client",
password : "Something non encrypted",
fullCrediCardNumber : "0000 0000 0000 0000"
}
Now, on my meteor client folder, I've this struncture...
collection clients.ts (server folder)
export var Clients = new Mongo.Collection('clients');
component client.ts (not server folder)
import {Clients} from '../collections/clients.ts';
class MyClients {
clients: Array<Object>;
constructor(zone: NgZone) {
this.clients = Clients.find();
}
}
..and for last: the html page to render it, but just display the name of the clients:
<li *ngFor="#item of clients">
{{client.name}}
</li>
Ok so far. but my concern is: In angular 1 & 2 applications the component or controller or directive runs on the client side, not server side.
I set my html just to show the name of the client. but since it's ah html rendering, probably with some skill is pretty easy to inject some code into the HTML render on angular to display all my fields.
Or could be easy to go to the console and type some commands to display the entire object from the database collection.
So, my question is: How safe meteor is in this sense ? Does my concerns correct ? Is meteor capable to protect my data , protect the name of the collections ? I know that I can specify on the find() to not bring me those sensitive data, but since the find() could be running not on the server side, it could be easy to modify it on the fly, no ?
Anyway...I will appreciate explanations about how meteor is safe (or not) in this sense.
ty !
You can protect data by simply not publishing any sensitive data on the server side.
Meteor.publish("my-clients", function () {
return Clients.find({
contractorId: this.userId // Publish only the current user's clients
}, {
name: 1, // Publish only the fields you want the browser to know of
phoneNumber: 1
});
});
This example only publishes the name and address fields of the currently logged in user's clients, but not their password or fullCreditCardNumber.
Another good example is the Meteor.users collection. On the server it contains all user data, login credentials, profiles etc. for all users. But it's also accessible on the client side. Meteor does two important things to protect this very sensitive collection:
By default it only publishes one document: the user that's logged in. If you type Meteor.users.find().fetch() into the browser console, you'll only see the currently logged in user's data, and there's no way on the client side to get the entire MongoDB users collection. The correct way to do this is to restrict the amount of published documents in your Meteor.publish function. See my example above, or 10.9 in the Meteor publish and subscribe tutorial.
Not the entire user document gets published. For example OAuth login credentials and password hashes aren't, you won't find them in the client-side collection. You can always choose which part of a document gets published, a simple way to do that is using MongoDB projections, like in the example above.
For instance, when selling a subscription to a user - what the system will do is
create an organisation
create a user
create a subscription
create an authentication
create send out an email
more operations based on business logic
And ALL above need to happen in SAME DB transaction as unit of work.
In SOAP semantic, it can be abstracted as register(organisation, User, Plan, authentication details..more parameters) and returns a subscription object.
But in Restful World, we will only deal with resources (only noun in URL) with HTTP verbs, and I found it is very hard to describe such business related logic instead of simple CRUD?
There is no requirement for RESTFUL interfaces that they are mapped 1:1 to a database behind the API.
The logic in your case could be:
client -- POST: SubscriptionRequests(request) --> Server
client <-- RESPONSE: Status|Error -- Server
Upon success, the Status response could contain properties which contain URI's to resulting new entries. Such as: SubscriptionURI = "Subscriptions/ID49343" UserURI="Users/User4711".
And then someone could later on ask about active subscriptions with:
client -- GET: Subscriptions --> Server
client <-- RESPONSE: Subscriptions | Error -- Server
This scheme could be considered RESTful. There is no problem with the fact, that the server has to manipulate a database (invisible to the client) and how it does that.
There is also not a problem that subsequent GET operations on the Subscriptions resource (and Users resource, for that matter) yield different output compared to before the SubscriptionRequest operation having been executed.
There is also no compelling reason to create a more chatty interface, just because you happen to have a certain data base modeling behind.
In that sense, it would be worse if you created an API like:
client -- POST: Users(newUser) --> Server
client <-- RESPONSE: Status|Error -- Server
(if adding user worked bla bla ... )
client -- POST: Subscriptions(userId,other data..) --> Server
client <-- RESPONSE: Status|Error -- Server
Which would basically just mean you did not design your API but simply copied the structure of your data base tables behind (and those will change next week).
In summary, it is not the business of API design to care about how the implementation handles the data base. If you need transactions or if you use some other ways to make sure all those things which need to be done are done is up to the implementation of that SubscriptionRequests.POST handler.
In fact, you think using the RPC mode ;-)
With REST, you must think using resources and representations. What you want to do is adding a subscription, so I would suggest to have a list resource for subscription with a method POST that implements the registration. In the request payload, you will provide what you need for the subscription and get back hints regarding the created subscription.
Here is a sample of the request:
POST /subscriptions/
{
"organization": {
"id": "organizationId",
"name": "organization name",
(...)
},
"user": {
"lastName": "",
(...)
}
}
Here is a sample of the response:
{
"id": "subscriptionId",
"credentials": {
(...)
},
(...)
}
You can notice that the payloads are proposals and perhaps don't exactly match to your subscription, user, ... structures. So feel free to adapt them.
Hope it helps you,
Thierry