Amazon Dynomo DB:BatchPutItem 因为 APPSYNC_ASSUME_ROLE 和类型不匹配错误
Amazon Dynomo DB: BatchPutItem because of APPSYNC_ASSUME_ROLE and type mismatch error
我正在使用 AWS Amplify 构建一个 React 应用程序。我使用 Cognito 用户池进行身份验证,并使用 GraphQL AppSync 后端作为我的后端。
我正在尝试编写一个自定义解析器来批量修改。这是我使用的架构:
type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
title: String!
description: String
completed: Boolean
}
input CreateTodoInput {
id: ID
title: String!
description: String
completed: Boolean
}
type Mutation {
batchAddTodos(todos: [CreateTodoInput]): [Todo]
}
此架构使用 Cognito User Pools.
为 GraphQL API 启用身份验证
为了让这个自定义突变起作用,需要添加 custom resolvers。我更改了 amplify/api/<your-api-name>/stacks/CustomResources.json
以包含以下资源:
// Left everything as it was
"Resources": {
"EmptyResource": {
"Type": "Custom::EmptyResource",
"Condition": "AlwaysFalse"
},
"BatchAddTodosResolver": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"DataSourceName": "TodoTable",
"TypeName": "Mutation",
"FieldName": "batchAddTodos",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
}
},
// ... more code that I didn't touch
对于自定义请求解析器,我编写了以下模板:
#foreach($item in ${ctx.args.todos})
## [Start] Owner Authorization Checks **
#set( $isOwnerAuthorized = false )
## Authorization rule: { allow: "owner", ownerField: "owner", identityField: "cognito:username" } **
#set( $allowedOwners0 = $util.defaultIfNull($item.owner, null) )
#set( $identityValue = $util.defaultIfNull($ctx.identity.claims.get("username"),
$util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____")) )
#if( $util.isList($allowedOwners0) )
#foreach( $allowedOwner in $allowedOwners0 )
#if( $allowedOwner == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
#end
#if( $util.isString($allowedOwners0) )
#if( $allowedOwners0 == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
#if( $util.isNull($allowedOwners0) && (! $item.containsKey("owner")) )
$util.qr($item.put("owner", $identityValue))
#set( $isOwnerAuthorized = true )
#end
## [End] Owner Authorization Checks **
## [Start] Throw if unauthorized **
#if( !($isStaticGroupAuthorized == true || $isDynamicGroupAuthorized == true || $isOwnerAuthorized
== true) )
$util.unauthorized()
#end
## [End] Throw if unauthorized **
#end
#set($todosdata = [])
#foreach($item in ${ctx.args.todos})
$util.qr($item.put("createdAt", $util.time.nowISO8601()))
$util.qr($item.put("updatedAt", $util.time.nowISO8601()))
$util.qr($item.put("__typename", "Todo"))
$util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId())))
$util.qr($todosdata.add($util.dynamodb.toMapValues($item)))
#end
{
"version": "2018-05-29",
"operation": "BatchPutItem",
"tables": {
"TodoTable": $utils.toJson($todosdata)
}
}
在第一个循环中,我试图验证用户是否有权访问他创建的待办事项。在第二个循环中,我添加了由 Amplify CLI 生成的解析器添加的数据。这包括 __typename
、时间戳和 id
.
然后我请求创建资源。我关注了this tutorial for the code。请注意,我必须将版本更新为 "2018-05-29"
。 Amplify CLI 生成的代码通常有一个版本"2017-02-28"
(我不知道这是否重要)。
我还为响应解析器编写了以下映射:
#if ($ctx.error)
$util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end
$util.toJson($ctx.result.data)
这基本上告诉 AppSync return 数据和所有未处理项目的错误。
我第一次尝试使用 React 发出请求:
import API, { graphqlOperation } from '@aws-amplify/api';
// ... later
async function handleClick() {
const todoFixtures = [
{ id: 1, title: 'Get groceries', description: '', completed: false },
{ id: 2, title: 'Go to the gym', description: 'Leg Day', completed: true }
];
try {
const input = { todos: prepareTodos(todoFixtures) };
const res = await API.graphql(graphqlOperation(batchAddTodos, input));
console.log(res);
} catch (err) {
console.log('error ', err);
}
}
prepareTodos
只是摆脱了 id
字段并将空字段设置为 null
(以避免 DynamoDB 对我大喊大叫)。代码在底部,因为它是无关紧要的。
由于失败,我尝试通过 AppSync 控制台进行更改:
mutation add {
batchAddTodos(todos: [
{title: "Hello", description: "Test", completed: false}
]) {
id title
}
}
但是两次尝试都抛出以下错误:
{
"data": {
"batchAddTodos": null
},
"errors": [
{
"path": [
"batchAddTodos"
],
"data": null,
"errorType": "DynamoDB:AmazonDynamoDBException",
"errorInfo": null,
"locations": [
{
"line": 32,
"column": 3,
"sourceName": null
}
],
"message": "User: arn:aws:sts::655817346595:assumed-role/Todo-role-naona7ytt5drxazwmtp7a2uccy-batch/APPSYNC_ASSUME_ROLE is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:eu-central-1:655817346595:table/TodoTable (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException; Request ID: EP48SJPVMB9G9M69HPR0BO8SKJVV4KQNSO5AEMVJF66Q9ASUAAJG)"
},
{
"path": [
"batchAddTodos"
],
"locations": null,
"message": "Can't resolve value (/batchAddTodos) : type mismatch error, expected type LIST"
}
]
}
这让我相信 React 代码 "correct" 或者至少与 AppSync 代码一样错误。但我怀疑错误出在解析器模板映射中的某个地方。我只是找不到它。这里出了什么问题?
可能生成的 assume-role 不支持 "2018-05-19"
版本?下面是角色默认生成角色的代码(这个不是我写的):
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "DynamoDBAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
},
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}/*",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
}
]
}
]
}
}
]
prepareTodos
:
const map = f => arr => arr.map(f);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const dissoc = prop => ({ [prop]: _, ...obj }) => obj;
const mapObj = f => obj =>
Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: f(obj[key]) }), {});
const replaceEmptyStringWithNull = x => (x === '' ? null : x);
const prepareTodos = map(
pipe(
dissoc('id'),
mapObj(replaceEmptyStringWithNull)
)
);
编辑: 我设法解决了类型不匹配问题。在回复中我必须 return: $util.toJson($ctx.result.data.TodoTable)
.
$util.toJson($ctx.result.data.TodoTable)
在响应模板中,以及将 TodoTable
更改为实际的 table 名称(您可以在控制台的 DynamoDB 下查找它,它看起来像这样 Todo-dqeronnsgvd2pf3facjmlgtsjk-master
) 解决了错误。
另外 here 是我在偶然发现这个问题时编写的分步教程。
我正在使用 AWS Amplify 构建一个 React 应用程序。我使用 Cognito 用户池进行身份验证,并使用 GraphQL AppSync 后端作为我的后端。
我正在尝试编写一个自定义解析器来批量修改。这是我使用的架构:
type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
title: String!
description: String
completed: Boolean
}
input CreateTodoInput {
id: ID
title: String!
description: String
completed: Boolean
}
type Mutation {
batchAddTodos(todos: [CreateTodoInput]): [Todo]
}
此架构使用 Cognito User Pools.
为 GraphQL API 启用身份验证为了让这个自定义突变起作用,需要添加 custom resolvers。我更改了 amplify/api/<your-api-name>/stacks/CustomResources.json
以包含以下资源:
// Left everything as it was
"Resources": {
"EmptyResource": {
"Type": "Custom::EmptyResource",
"Condition": "AlwaysFalse"
},
"BatchAddTodosResolver": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"DataSourceName": "TodoTable",
"TypeName": "Mutation",
"FieldName": "batchAddTodos",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
}
},
// ... more code that I didn't touch
对于自定义请求解析器,我编写了以下模板:
#foreach($item in ${ctx.args.todos})
## [Start] Owner Authorization Checks **
#set( $isOwnerAuthorized = false )
## Authorization rule: { allow: "owner", ownerField: "owner", identityField: "cognito:username" } **
#set( $allowedOwners0 = $util.defaultIfNull($item.owner, null) )
#set( $identityValue = $util.defaultIfNull($ctx.identity.claims.get("username"),
$util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____")) )
#if( $util.isList($allowedOwners0) )
#foreach( $allowedOwner in $allowedOwners0 )
#if( $allowedOwner == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
#end
#if( $util.isString($allowedOwners0) )
#if( $allowedOwners0 == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
#if( $util.isNull($allowedOwners0) && (! $item.containsKey("owner")) )
$util.qr($item.put("owner", $identityValue))
#set( $isOwnerAuthorized = true )
#end
## [End] Owner Authorization Checks **
## [Start] Throw if unauthorized **
#if( !($isStaticGroupAuthorized == true || $isDynamicGroupAuthorized == true || $isOwnerAuthorized
== true) )
$util.unauthorized()
#end
## [End] Throw if unauthorized **
#end
#set($todosdata = [])
#foreach($item in ${ctx.args.todos})
$util.qr($item.put("createdAt", $util.time.nowISO8601()))
$util.qr($item.put("updatedAt", $util.time.nowISO8601()))
$util.qr($item.put("__typename", "Todo"))
$util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId())))
$util.qr($todosdata.add($util.dynamodb.toMapValues($item)))
#end
{
"version": "2018-05-29",
"operation": "BatchPutItem",
"tables": {
"TodoTable": $utils.toJson($todosdata)
}
}
在第一个循环中,我试图验证用户是否有权访问他创建的待办事项。在第二个循环中,我添加了由 Amplify CLI 生成的解析器添加的数据。这包括 __typename
、时间戳和 id
.
然后我请求创建资源。我关注了this tutorial for the code。请注意,我必须将版本更新为 "2018-05-29"
。 Amplify CLI 生成的代码通常有一个版本"2017-02-28"
(我不知道这是否重要)。
我还为响应解析器编写了以下映射:
#if ($ctx.error)
$util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end
$util.toJson($ctx.result.data)
这基本上告诉 AppSync return 数据和所有未处理项目的错误。
我第一次尝试使用 React 发出请求:
import API, { graphqlOperation } from '@aws-amplify/api';
// ... later
async function handleClick() {
const todoFixtures = [
{ id: 1, title: 'Get groceries', description: '', completed: false },
{ id: 2, title: 'Go to the gym', description: 'Leg Day', completed: true }
];
try {
const input = { todos: prepareTodos(todoFixtures) };
const res = await API.graphql(graphqlOperation(batchAddTodos, input));
console.log(res);
} catch (err) {
console.log('error ', err);
}
}
prepareTodos
只是摆脱了 id
字段并将空字段设置为 null
(以避免 DynamoDB 对我大喊大叫)。代码在底部,因为它是无关紧要的。
由于失败,我尝试通过 AppSync 控制台进行更改:
mutation add {
batchAddTodos(todos: [
{title: "Hello", description: "Test", completed: false}
]) {
id title
}
}
但是两次尝试都抛出以下错误:
{
"data": {
"batchAddTodos": null
},
"errors": [
{
"path": [
"batchAddTodos"
],
"data": null,
"errorType": "DynamoDB:AmazonDynamoDBException",
"errorInfo": null,
"locations": [
{
"line": 32,
"column": 3,
"sourceName": null
}
],
"message": "User: arn:aws:sts::655817346595:assumed-role/Todo-role-naona7ytt5drxazwmtp7a2uccy-batch/APPSYNC_ASSUME_ROLE is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:eu-central-1:655817346595:table/TodoTable (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException; Request ID: EP48SJPVMB9G9M69HPR0BO8SKJVV4KQNSO5AEMVJF66Q9ASUAAJG)"
},
{
"path": [
"batchAddTodos"
],
"locations": null,
"message": "Can't resolve value (/batchAddTodos) : type mismatch error, expected type LIST"
}
]
}
这让我相信 React 代码 "correct" 或者至少与 AppSync 代码一样错误。但我怀疑错误出在解析器模板映射中的某个地方。我只是找不到它。这里出了什么问题?
可能生成的 assume-role 不支持 "2018-05-19"
版本?下面是角色默认生成角色的代码(这个不是我写的):
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "DynamoDBAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
},
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}/*",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
}
]
}
]
}
}
]
prepareTodos
:
const map = f => arr => arr.map(f);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const dissoc = prop => ({ [prop]: _, ...obj }) => obj;
const mapObj = f => obj =>
Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: f(obj[key]) }), {});
const replaceEmptyStringWithNull = x => (x === '' ? null : x);
const prepareTodos = map(
pipe(
dissoc('id'),
mapObj(replaceEmptyStringWithNull)
)
);
编辑: 我设法解决了类型不匹配问题。在回复中我必须 return: $util.toJson($ctx.result.data.TodoTable)
.
$util.toJson($ctx.result.data.TodoTable)
在响应模板中,以及将 TodoTable
更改为实际的 table 名称(您可以在控制台的 DynamoDB 下查找它,它看起来像这样 Todo-dqeronnsgvd2pf3facjmlgtsjk-master
) 解决了错误。
另外 here 是我在偶然发现这个问题时编写的分步教程。