AWS AppSync Null #connection query result - aws-amplify

I'm working to create a web app using Amplify, and would like to create a relationship between two tables to keep things DRY. I have 2 tables: Listing and Service. Each Listing should be related to one Service. My (abbreviated) schema looks like this. All queries / resolvers are auto-generated by amplify push
type Listing
#model
#auth(rules: [{ allow: public, operations: [read] }])
{
id: ID!
status: Status
source: String
service: String
serviceDetail: Service #connection (keyName: "service")
}
type Service #model {
id: ID!
name: String
homepage: AWSURL
logo: String
}
To eliminate any other issues, I'm running my tests in the AppSync Console.
Running the ListServices query returns all expected data from the Service table without any errors. Running a getService query on a specific id returns the expected data without any errors.
Running the ListListings query returns with no errors and all expected data from the Listings table. The serviceDetail field only contains null.
I've been digging through the documentation all morning, and can't figure out what's wrong.

So, I defined the #connection wrong. The correct syntax is:
ServiceDetail: Service #connection (fields: ["service"])

Related

AWS Amplify Build Issue - StackUpdateComplete

When running amplify push -y in the CLI, my project errors with this message:
["Index: 0 State: {\"deploy\":\"waitingForDeployment\"} Message: Resource is not in the state stackUpdateComplete"]
How do I resolve this error?
The "Resource is not in the state stackUpdateComplete" is the message that comes from the root CloudFormation stack associated with the Amplify App ID. The Amplify CLI is just surfacing the error message that comes from the update stack operation. This indicates that the Amplify's CloudFormation stack may have been still be in progress or stuck.
Solution 1 – “deployment-state.json”:
To fix this issue, go to the S3 bucket containing project settings and deleted the “deployment-state.json” file in root folder as this file holds the app deployment states. The bucket should end with, or contain the word “deployment”.
Solution 2 – “Requested resource not found”:
Check the status of the CloudFormation stack and see if you can notice that the stack failed because of a “Requested resource not found” error indicating that the DynamoDB table “tableID” was missing and confirm that you have deleted it (possibly accidentally). Manually create the above DynamoDB table and retry to push again.
Solution 3A - “#auth directive with 'apiKey':
If you recieve an error stating that “#auth directive with 'apiKey' provider found, but the project has no API Key authentication provider configured”. This error appears when you define a public authorisation in your GraphQL schema without specifying a provider. The public authorization specifies that everyone will be allowed to access the API, behind the scenes the API will be protected with an API Key. To be able to use the public API you must have API Key configured.
The #auth directive allows the override of the default provider for a given authorization mode. To fix the issue specify “IAM” as the provider which allows to use an "Unauthenticated Role" from Cognito Identity Pools for public access instead of an API Key.
Below is the sample code for public authorisation rule:
type Todo #model #auth(rules: [{ allow: public, provider: iam, operations: [create, read, update, delete] }]) {
id: ID!
name: String!
description: String
}
After making the above changes, you can run “amplify update api” and add a IAM auth provider, the CLI generated scoped down IAM policies for the "UnAuthenticated" role automatically.
Solution 3B - Parameters: [AuthCognitoUserPoolId] must have values:
Another issue could occur here, where the default authorization type is API Key when you run the command “amplify add api” without specifying the API type. To fix this issue, follow these steps:
Deleted the the API
Recreate a new one by specifying the “Amazon Cognito user pool” as the authorization mode
Add IAM as an additional authorization type
Re-enable #auth directive in the newly created API Schema
Run “amplify push”
Documentation:
Public Authorisation
Troubleshoot CloudFormation stack issues in my AWS Amplify project

How to retrieve member list from Silverstripe API

I am developing a platform and I need to get the members emails from an existing Silverstripe installation. I think it is V4 but not sure yet.
I was hoping to call a REST API but I can't seem to find any information about how you would go about doing this. I would need to call this each day to get the latest members.
Is this possible or is there another way to go about doing this?
I had a look at the API documentation but the information is not helpful and it does not have an explanations or examples. https://api.silverstripe.org/4/index.html
Silverstripe 4 does not expose its data through a REST API service out of the box. We can install a module to allow us to do this.
Rest API module:
https://github.com/colymba/silverstripe-restfulapi
Rest API module:
https://github.com/silverstripe/silverstripe-restfulserver
An alternative is to use the Silverstripe GraphQL module to retrieve data:
https://github.com/silverstripe/silverstripe-graphql
You can do this almost out of the box with the SilverStripe GraphQL API in SilverStripe 4. In addition to 3dgoo's answer, here's a bit of a guide for you:
Out of the box configuration
SilverStripe exposes an "admin" GraphQL server, which requires you to be logged in to use it. If you want to use it from another server, you can base64 encode your basic authentication credentials and pass it as a header. More info on this here.
The SilverStripe CMS module already exposes member's first and last names, since they're used by parts of the CMS through the GraphQL API already. If you want an email address then you can add that with some basic YAML in your app folder.
Adding the member's email field
Add some custom configuration to your app/_config folder - configuration in SilverStripe is merged, so the array values fields: [Email] will merge with the CMS values linked above
# File: app/_config/graphql.yml
---
Name: appgraphql
---
SilverStripe\GraphQL\Manager:
schemas:
admin:
scaffolding:
types:
SilverStripe\Security\Member:
fields: [Email]
operations:
read: true
Note that I've also added operations.read: true to this, because the CMS will only let you read members one at a time via the readOne operation. For your case you'll want to enable read, which returns a paginated list. More info on available operations.
Testing your query
The easiest way to do this is to install GraphiQL (via silverstripe-graphql-devtools), a web (or app) based UI for inspecting GraphQL schemas and running queries against your server. This can be done easily with Composer:
composer require --dev silverstripe/graphql-devtools dev-master
Open up your browser to http://localhost/dev/graphiql?flush. Replace localhost with whatever your SilverStripe server is running on. You add ?flush to the querystring to tell SilverStripe to flush its cache (YAML and PHP files) to pick up your new module and config.
When you get your GraphiQL query editor you can start by writing query GetUsers { ... } and you'll notice that as you type deeper into the query it autocompletes the available options for you.
Here's the query to retrieve your member email addresses:
query GetUserEmails {
readSilverStripeMembers {
edges {
node {
Email
}
}
}
}
Micro-explanation: GetUserEmails is an arbitrary query name you create. You don't actually need to write one, it'll work fine without. readSilverStripeMembers is an automatically scaffolded query name, which happens because you enabled read: true in the GraphQL operations. If you delete it and start typing it again you'll see the other options available as well, the one that ships out of the box with the CMS is readOneSilverStripeMember. The edges and node levels are for pagination.
Using the query
It sounds to me like your SilverStripe server is already running somewhere, and you may not have a local version to test. If that's the case, simply adding the YAML configuration above to your app folder and deploying it will be enough to get your server to give member emails in admin GraphQL calls, then you can do your GraphQL queries with cURL or something:
curl 'http://localhost/admin/graphql/' \ # Adjust to your domain
-H 'Authorization: Basic YWRtaW46YWRtaW4=' \ # This is admin:admin base64 encoded
-H 'Content-Type: application/json' \ # Required for the input data structure
--data-binary '{"query":"query { readSilverStripeMembers { edges { node { Email } } } }","variables":null}'
Example output:
{
"data": {
"readSilverStripeMembers": {
"edges": [
{
"node": {
"Email": "leslie.lawless#example.com"
}
},
{
"node": {
"Email": "mika#example.com"
}
},
{
"node": {
"Email": "sam#example.com"
}
}
]
}
}
}

Using AWS AppSync (with amplify), how does one allow authenticated users read-only access, but only allow mutations for object owners?

I'm using Cognito User Pools as the default authentication method. I'm also using iam for my lambda backend. I'm using an aws appsync client in the lambda function for some custom resolvers.
let's assume I have a User object type that fundamentally looks like this:
type User {
id: ID!
displayName: String!
}
What I want to be able to do:
Allow full read/write access for the object owner.
Allow the lambda function (with iam) full read/write access.
Allow read-only access for users who are authenticated through cognito user pools, but are not the owner of the object.
I've been picking and prodding with the #auth directive attempting to get the results I'm looking for but nothing has been able to work. I've looked at the documentation at AWS GraphQL Transform Docs and I seem to be a bit confused.
Here's what I've tried:
type User
#model
#auth(rules: [
{ allow: owner, operations: [create, update, delete] }
{ allow: private, provider: iam, operations: [update, delete] }
]) {
id: ID!
displayName: String!
}
To my understanding, by removing read from the operations list in the #auth directive removes the check on get and list queries. What am I doing wrong? How do I achieved my desired results?
EDIT: To clarify, I've already enabled multiple authorization types. (cognito user pools by default and iam for the lambda resolvers). My question is: How do I use the #auth directive to get the intended results?
AuthProvider { apiKey iam oidc userPools }
So, I hope this can help you(its worked for me :-))
type User #model #auth(rules: [
{ allow: owner ,operations: [create, update, delete]},
{ allow: private, provider: iam, operations: [read, update, delete] }
{ allow: private, provider: userPools, operations: [read] }
]) {
id: ID!
name: String!
}
To enable both IAM and Cognito, you'll need to follow the instructions on using multiple authorization types: https://aws.amazon.com/blogs/mobile/using-multiple-authorization-types-with-aws-appsync-graphql-apis/
E.g. specifying #aws_iam or #aws_cognito_user_pools on the respective fields
For your use case, you'll probably then also need to add some code in your resolvers to control which users can perform which actions, as described in: https://docs.aws.amazon.com/appsync/latest/devguide/security-authorization-use-cases.html
E.g. along the lines of #if($context.result["Owner"] == $context.identity.username)
If you're looking to do this purely through the Amplify CLI, this person seems to have worked through a very similar problem: https://github.com/aws-amplify/amplify-cli/issues/2694

DynamoDB, can't save encrypted data, "not authorized to perform: kms:GenerateDataKey on resource"

I'm trying to save encrypted data to DynamoDB with the help of the "Amazon DynamoDB Encryption Client for Java".
I also use the Serverless framework to deploy my application (some Lamdba functions using DynamoDB). The AWS Lamdba functions are written in Kotlin.
I have this in my serverless.yml in the iamRoleStatements section (under the provider section):
- Effect: “Allow”
Action:
- “kms:GenerateDataKey”
Resource: “*”
I thought that it should be enough but I'm getting this error when I'm trying to save encrypted data to DynamoDB:
com.amazonaws.services.kms.model.AWSKMSException: User: arn:aws:sts::120102300450:assumed-role/appname-username-eu-west-1-lambdaRole/appname-username-functionname is not authorized to perform: kms:GenerateDataKey on resource: arn:aws:kms:eu-west-1:120102300450:key/12d3f45c-6fff-0007-b123-5bfe5678e012 (Service: AWSKMS; Status Code: 400; Error Code: AccessDeniedException; Request ID: ...)
(the alphanumeric IDs are obfuscated here to not disclose real data)
I also tried to add several other KMS permissions but it didn't help:
- Effect: "Allow"
Action:
- "kms:GenerateDataKey"
- "kms:GenerateDataKeyWithoutPlaintext"
- "kms:CreateAlias"
- "kms:CreateKey"
- "kms:Decrypt"
- "kms:Encrypt"
- "kms:EnableKey"
- "kms:UpdateAlias"
Resource: "*"
So, the whole purpose of the "Amazon DynamoDB Encryption Client for Java" cannot be achieved in this case.
What is missing here?
I've found what was the problem. I used the default KMS key, and I had to use a custom key. Then it worked.

How to add business keys/unique constraint in GraphQL - AppSync AWS

Here is my definition of an entity/model in GraphQL SDL that is used by AWS Amplify to create an App Sync API and its table in DynamoDB.
I want to make the name and author as the business keys. Is there a way to achieve this? I believe Prism has #Unique directive for the same. Or if it is not possible, can a constraint be added in the table in DynamoDB?
type Post
#model
#versioned
#auth(rules: [{allow: owner}])
{
id: ID!
name: String!
author: String!
description: String
}

Resources