I have the following resolver, allowing me to retrieve information about the current user company (companyId is added as a custom field on the cognito user pool). The field on cognito is set to mutable.
{
"version" : "2017-02-28",
"operation" : "GetItem",
"key": {
"id" : $util.dynamodb.toDynamoDBJson($context.identity.claims.get("custom:companyId"))
}
}
This works fine when using the AWS AppSync interface (after login in) as the logs show:
{
"errors": [],
"mappingTemplateType": "Request Mapping",
"path": "[getMyClientCompany]",
"resolverArn": "arn:aws:appsync:eu-west-1:261378271140:apis/rue25cac6jc6vfbhvu32sjafqy/types/Query/fields/getMyClientCompany",
"transformedTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"GetItem\",\n \"key\": {\n \"id\" : {\"S\":\"0c1c81db-a771-4856-9a30-d11bf8e3cab1\"}\n }\n}",
"context": {
"arguments": {},
"source": null,
"result": null,
"error": null,
"outErrors": []
},
"fieldInError": false
}
But doesn't work when the code comes from Amplify-js:
{
"errors": [],
"mappingTemplateType": "Request Mapping",
"path": "[getMyClientCompany]",
"resolverArn": "arn:aws:appsync:eu-west-1:261378271140:apis/rue25cac6jc6vfbhvu32sjafqy/types/Query/fields/getMyClientCompany",
"transformedTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"GetItem\",\n \"key\": {\n \"id\" : {\"NULL\":null}\n }\n}",
"context": {
"arguments": {},
"source": null,
"result": null,
"error": null,
"outErrors": []
},
"fieldInError": false
}
The key that should be "custom:companyId" is "NULL" now
I imagine the issue is either with Amplify (version 0.4.8) or with the cognito user resolver for some reason
Any idea what could be going on?
There are two JWT tokens Cognito may utilize. ID and Access. ID token seems to contain those custom claims.
From Amplify you tweak the Authorization header to use ID token vs Access token.
Here's the code, put it in AWS Amplify configuration:
API: {
graphql_endpoint: 'https://****.appsync-api.***.amazonaws.com/graphql',
graphql_region: '***',
graphql_authenticationType: 'AMAZON_COGNITO_USER_POOLS',
graphql_headers: async () => {
try {
const token = (await Auth.currentSession()).idToken.jwtToken;
return { Authorization: token }
}
catch (e) {
console.error(e);
return {};
// Potentially you can retrieve it from local storage
}
}
}
Note, there seem to be several different keys to configure Amplify keys:
for example, aws_appsync_graphqlEndpoint vs API { graphql_endpoint }, I used the latter.
Related
I recently rewrote some GraphQL services from Java to .NET Core.
In Java, I was able to provide custom error messages to the clients using the errors.extensions in the response, ie:
{
"data": {
"someMutation": null
},
"errors": [{
"cause": null,
"message": "Unauthorized",
"httpStatusCode": 0,
"extensions": {
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
"httpStatusDescription": "Unauthorized",
"httpStatusCode": 401
},
"errorType": "ValidationError",
"path": null,
"localizedMessage": "Unauthorized",
"suppressed": []
}
]
}
However, in .NET, I don't seem to be able to replicate this format.
ErrorInfo.Extensions is added to the root of the response, not to the the Errors object itself, eg:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Auth token not provided"
}
],
"extensions": {
"httpStatusCode": 401,
"httpStatusDescription": null,
"uiMessage": "Oh no, your session expired. You'll need to login again to continue.",
}
}
The GraphQL spec reads (ref https://spec.graphql.org/October2021/#sec-Errors, https://spec.graphql.org/October2021/#example-8b658):
GraphQL services may provide an additional entry to errors with key
extensions. This entry, if set, must have a map as its value. This
entry is reserved for implementors to add additional information to
errors however they see fit, and there are no additional restrictions
on its contents.
eg:
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{ "line": 6, "column": 7 }],
"path": ["hero", "heroFriends", 1, "name"],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
I created a new test project (.NET Core 3.1) using the latest versions of the libraries (GraphQL 7.1.1 et al) but am still unable to add custom properties to errors.extensions.
This is the test mutation which intentionally throws an exception:
Field<StringGraphType>("greet")
.Argument<NonNullGraphType<StringGraphType>>("name")
.Resolve(context => {
try {
throw new Exception("Invalid input");
return "Hello " + context.GetArgument<String>("name");
} catch(Exception ex) {
// This doesn't seem to get returned anywhere in the response
Dictionary<String, object> extraData = new Dictionary<string, object>();
extraData.Add("error1", "message1");
// Add the error to the response using the overloaded constructor
context.Errors.Add(new ExecutionError("Oh dear, that went wrong", extraData));
// This gets added to the root of the response
context.OutputExtensions.Add("error2", "message2");
return null;
}
});
the mutation to invoke it:
mutation {greet(name:"Chewbacca")}
and the response (I don't know where errors.extensions.details comes from):
{
"errors": [
{
"message": "Oh dear, that went wrong",
"extensions": {
"details": "GraphQL.ExecutionError: Oh dear, that went wrong"
}
}
],
"data": {
"greet": null
},
"extensions": {
"error2": "message2"
}
}
I would imagine that the GraphQL.NET library would expose an Extensions dictionary on the ExecutionError object so one could add custom values in the usual manner, eg:
ExecutionError executionError = new ExecutionError("Oh dear, that went horribly wrong");
executionError.Extensions.Add("customError", "Your custom error here")
context.Errors.Add(executionError);
Which would result in a response similar to this:
{
"data": {
"someMutation": null
},
"errors": [{
"message": "Oh dear, that went horribly wrong",
"extensions": {
"customError": "Your custom error here"
}
}
]
}
I am hopeful that some bright individual in the community can (slap me upside the head and) point me in the right direction.
How to update the cache, after creating new data?
Error message from Apollo
Store error: the application attempted to write an object with no provided id but the store already contains an id of UsersPermissionsUser:1 for this object. The selectionSet that was trying to be written is:
{
"kind": "Field",
"name": { "kind": "Name", "value": "user" },
"arguments": [],
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{ "kind": "Field", "name": { "kind": "Name", "value": "username" }, "arguments": [], "directives": [] },
{ "kind": "Field", "name": { "kind": "Name", "value": "__typename" } }
]
}
}
Nativescript-vue Front-end Details
1- Watch Book Mobile app in action on YouTube: https://youtu.be/sBM-ErjXWuw
2- Watch Question video for details on YouTube: https://youtu.be/wqvrcBRQpZg
{N}-vue AddBook.vue file
apolloClient
.mutate({
// Query
mutation: mutations.CREATE_BOOK,
// Parameters
variables: {
name: this.book.name,
year: this.book.year,
},
// HOW TO UPDATE
update: (store, { data }) => {
console.log("data ::::>> ", data.createBook.book);
const bookQuery = {
query: queries.ALL_BOOKS,
};
// TypeScript detail: instead of creating an interface
// I used any type access books property without compile errors.
const bookData:any = store.readQuery(bookQuery);
console.log('bookData :>> ', bookData);
// I pin-pointed data objects
// Instead of push(createBook) I've pushed data.createBook.book
bookData.books.push(data.createBook.book);
store.writeQuery({ ...bookQuery, data: bookData })
},
})
.then((data) => {
// I can even see ID in Result
console.log("new data.data id ::::: :>> ", data.data.createBook.book.id);
this.$navigateTo(App);
})
.catch((error) => {
// Error
console.error(error);
});
What are these "Book:9": { lines in the cache?
console.log store turns out:
"Book:9": {
"id": "9",
"name": "Hadi",
"year": "255",
"__typename": "Book"
},
"$ROOT_MUTATION.createBook({\"input\":{\"data\":{\"name\":\"Hadi\",\"year\":\"255\"}}})": {
You can see all front-end GitHub repo here
Download Android apk file
Our goal is to update the cache. Add Book Method is in here:
https://github.com/kaanguru/mutate-question/blob/c199f8dcc8e80e83abdbcde4811770b766befcb5/nativescript-vue/app/components/AddBook.vue#L39
Back-end details
However, this is a frontend question a running Strapi GraphQL Server is here: https://polar-badlands-01357.herokuapp.com/admin/
GraphQL Playground
USER: admin
PASSWORD: passw123
You can see GraphQL documentation
I have so much simple Strapi GrapQL Scheme:
If you want to test it using postman or insomnia you can use;
POST GraphQL Query URL: https://polar-badlands-01357.herokuapp.com/graphql
Bearer Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTkwODI3MzE0LCJleHAiOjE1OTM0MTkzMTR9.WIK-f4dkwVAyIlP20v1PFoflpwGmRYgRrsQiRFgGdqg
NOTE: Don't get confused with $navigateTo() it's just a custom method of nativescript-vue.
It turns out;
all code was correct accept bookData.push(createBook);
// HOW TO UPDATE
update: (store, { data }) => {
console.log("data ::::>> ", data.createBook.book);
const bookQuery = {
query: queries.ALL_BOOKS,
};
// TypeScript detail: instead of creating an interface
// I used any type access books property without compile errors.
const bookData:any = store.readQuery(bookQuery);
console.log('bookData :>> ', bookData);
// I pin-pointed data objects
// Instead of push(createBook) I've pushed data.createBook.book
bookData.books.push(data.createBook.book);
store.writeQuery({ ...bookQuery, data: bookData })
},
})
Typescipt was helping
The point is; I shouldn't trust TypeScript errors, or at least I should read more about what it really says.
Typescript just asked me to be more specific while saying: Property 'push' does not exist on type 'unknown'
TypeScript was trying to tell me I need to be more specific while calling ROOT_MUTATION data. It said: Cannot find name 'createBook' But again I ignored it.
Solution Github Branch
https://github.com/kaanguru/mutate-question/tree/solution
Sources
how to update cache
Create interface for object Typescript
We are using google login in our website and spring social library to implement oAuth 2. After getting accessToken , with scope as userInfo.email, we are trying to retrieve accountEmail. spring-social-google(version 1.0.0) is looking for key "account" in the emails property of the reponse. Below is code block from spring-social-sources org.springframework.social.google.api.plus.Person.class
public String getAccountEmail() {
if (emails != null) {
for (Entry<String, String> entry : emails.entrySet()) {
if (entry.getValue().equals("account")) {
return entry.getKey();
}
}
}
return null;
}
From Oct 27, google is responding with email map as "ACCOUNT" instead of "account"
Expert from logs before 27th - getEmails :: {xxxxxxx#gmail.com=account}
Expert from logs after 27th - getEmails :: {xxxxxxx#gmail.com=ACCOUNT}
I could not find any documentmentation from google regarding the change or reponse format. When i use API playground, i can see the type is coming as 'ACCOUNT'.
"image": {
"url": "....photo.jpg",
"isDefault": true },
"etag": "xxx",
"id": "xxx",
"kind": "plus#person",
"emails": [
{
"type": "ACCOUNT",
"value": "xxxx#gmail.com"
}
]
}
I just wanted to check if any one else faced the issue or if anyone has a documentation from google, which explains why the change happened.
I try to make a user / role authentication (session based) with CouchDB but as soon as I enter a role at a database all users and roles are can access the database -> the are not authorized.
Get the session:
POST http://myhost:1234/_session
It returns (the userCtx object):
{
"ok": true,
"name": "some_user_name",
"roles": [
"developers"
]
}
Then I added the roles to the database:
PUT http://myhost:1234/database/_security
{
"admins": {
"names": [],
"roles": []
},
"members": {
"names": [],
"roles": [
"developers"
]
}
}
and it returns {"ok":true} and I can see the permissions also in fauxton.
When I now try to access the database with
GET http://myhost:1234/database/_all_docs
it returns:
{
"error": "unauthorized",
"reason": "You are not authorized to access this db."
}
If you are using Postman, Add Following key-value in Headers
"Content-Type": "application/json",
Accept: "application/json",
Authorization: "Basic " + btoa("YOUR_ADMIN_USER:YOUR_PASSWORD")
ahhh I found the mistake, I was doing the tests with postman and there I did not recognized that the credentials was not send with the requests :-(
I'm attempting to customize the oauth2orize all-grants example for my use. I can run the all-grants as-is and everything works (as you would expect), but when I run my customized version, I always end up with this error:
Error: Unable to issue redirect for OAuth 2.0 transaction
at Object.response [as handle] (C:\Dev\Expy\api\node_modules\oauth2orize\lib\grant\code.js:122:41)
I've been digging into this a bit and it seems there is a property of the txn variable within that function that should be named redirectURI and should be populated with the redirect_uri from the query string of the initial request to the /dialog/authorize page. For some reason this doesn't happen on my example app. Is this caused by an express version difference? That is the biggest difference that I see between the example code and my customizations. The all-grants uses express 2.* and my app will use express 4.*.
If it isn't an express version issue, where should I start looking in my code for the issue?
For reference, this is what I see in my app for the txn object:
txn: {
"transactionID": "evlUd2q4",
"client": { ... },
"req": {
"type": "code",
"clientID": "5C3B4438-433F-11E5-A532-74653C701F13"
},
"user": { ... },
"res": {
"allow": true
}
}
and this is what I see in that same object with the example (note the presence of the redirectURI in req and in the txn itself):
txn: {
"transactionID": "EEcYp3Uj",
"client": { ... },
"redirectURI": "http://localhost:3000/api/userinfo",
"req": {
"type": "code",
"clientID": "abc123",
"redirectURI": "http://localhost:3000/api/userinfo"
},
"user": { ... },
"res": {
"allow": true
}
}