amplify-js
amplify-js copied to clipboard
Unable to use "iam" authorization for custom mutations in v6.2
Before opening, please confirm:
- [X] I have searched for duplicate or closed issues and discussions.
- [X] I have read the guide for submitting bug reports.
- [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
Next.js
Amplify APIs
GraphQL API
Amplify Version
v6
Amplify Categories
No response
Backend
Amplify Gen 2 (Preview)
Environment information
# Put output below this line
System:
OS: Windows 11 10.0.22631
CPU: (20) x64 13th Gen Intel(R) Core(TM) i9-13900H
Memory: 2.44 GB / 31.68 GB
Binaries:
Node: 18.19.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
npm: 10.2.4 - C:\Program Files\nodejs\npm.CMD
pnpm: 8.15.4 - ~\AppData\Local\pnpm\pnpm.EXE
Browsers:
Edge: Chromium (123.0.2420.97)
Internet Explorer: 11.0.22621.3527
npmPackages:
%name%: 0.1.0
@ampproject/toolbox-optimizer: undefined ()
@aws-amplify/backend: ^1.0.0 => 1.0.0
@aws-amplify/backend-cli: ^1.0.1 => 1.0.1
@aws-amplify/ui-react: ^6.1.9 => 6.1.9
@aws-amplify/ui-react-internal: undefined ()
@aws-sdk/s3-presigned-post: ^3.568.0 => 3.568.0
@babel/core: undefined ()
@babel/runtime: 7.22.5
@edge-runtime/cookies: 4.1.1
@edge-runtime/ponyfill: 2.4.2
@edge-runtime/primitives: 4.1.0
@hapi/accept: undefined ()
@heroicons/react: ^2.1.3 => 2.1.3
@mswjs/interceptors: undefined ()
@napi-rs/triples: undefined ()
@next/font: undefined ()
@opentelemetry/api: undefined ()
@types/node: ^20.12.8 => 20.12.8
@types/react: ^18.3.1 => 18.3.1
@types/react-dom: ^18.3.0 => 18.3.0
@vercel/nft: undefined ()
@vercel/og: 0.6.2
acorn: undefined ()
amphtml-validator: undefined ()
anser: undefined ()
arg: undefined ()
assert: undefined ()
async-retry: undefined ()
async-sema: undefined ()
autoprefixer: ^10.4.19 => 10.4.19
aws-amplify: ^6.2.0 => 6.2.0
aws-amplify/adapter-core: undefined ()
aws-amplify/analytics: undefined ()
aws-amplify/analytics/kinesis: undefined ()
aws-amplify/analytics/kinesis-firehose: undefined ()
aws-amplify/analytics/personalize: undefined ()
aws-amplify/analytics/pinpoint: undefined ()
aws-amplify/api: undefined ()
aws-amplify/api/server: undefined ()
aws-amplify/auth: undefined ()
aws-amplify/auth/cognito: undefined ()
aws-amplify/auth/cognito/server: undefined ()
aws-amplify/auth/enable-oauth-listener: undefined ()
aws-amplify/auth/server: undefined ()
aws-amplify/data: undefined ()
aws-amplify/data/server: undefined ()
aws-amplify/datastore: undefined ()
aws-amplify/in-app-messaging: undefined ()
aws-amplify/in-app-messaging/pinpoint: undefined ()
aws-amplify/push-notifications: undefined ()
aws-amplify/push-notifications/pinpoint: undefined ()
aws-amplify/storage: undefined ()
aws-amplify/storage/s3: undefined ()
aws-amplify/storage/s3/server: undefined ()
aws-amplify/storage/server: undefined ()
aws-amplify/utils: undefined ()
aws-cdk: ^2.140.0 => 2.140.0
aws-cdk-lib: ^2.140.0 => 2.140.0
aws-sdk: ^2.1613.0 => 2.1613.0
babel-packages: undefined ()
browserify-zlib: undefined ()
browserslist: undefined ()
buffer: undefined ()
bytes: undefined ()
ci-info: undefined ()
cli-select: undefined ()
client-only: 0.0.1
commander: undefined ()
comment-json: undefined ()
compression: undefined ()
conf: undefined ()
constants-browserify: undefined ()
constructs: ^10.3.0 => 10.3.0
content-disposition: undefined ()
content-type: undefined ()
cookie: undefined ()
cross-spawn: undefined ()
crypto-browserify: undefined ()
css.escape: undefined ()
data-uri-to-buffer: undefined ()
debug: undefined ()
devalue: undefined ()
domain-browser: undefined ()
edge-runtime: undefined ()
esbuild: ^0.20.2 => 0.20.2
eslint: ^8.57.0 => 8.57.0
eslint-config-next: 14.2.3 => 14.2.3
events: undefined ()
find-cache-dir: undefined ()
find-up: undefined ()
fresh: undefined ()
get-orientation: undefined ()
glob: undefined ()
gzip-size: undefined ()
http-proxy: undefined ()
http-proxy-agent: undefined ()
https-browserify: undefined ()
https-proxy-agent: undefined ()
icss-utils: undefined ()
ignore-loader: undefined ()
image-size: undefined ()
is-animated: undefined ()
is-docker: undefined ()
is-wsl: undefined ()
jest-worker: undefined ()
json5: undefined ()
jsonwebtoken: undefined ()
loader-runner: undefined ()
loader-utils: undefined ()
lodash.curry: undefined ()
lru-cache: undefined ()
mini-css-extract-plugin: undefined ()
nanoid: undefined ()
native-url: undefined ()
neo-async: undefined ()
next: 14.2.3 => 14.2.3
node-fetch: undefined ()
node-html-parser: undefined ()
ora: undefined ()
os-browserify: undefined ()
p-limit: undefined ()
path-browserify: undefined ()
picomatch: undefined ()
platform: undefined ()
postcss: ^8.4.38 => 8.4.38 (8.4.31)
postcss-flexbugs-fixes: undefined ()
postcss-modules-extract-imports: undefined ()
postcss-modules-local-by-default: undefined ()
postcss-modules-scope: undefined ()
postcss-modules-values: undefined ()
postcss-preset-env: undefined ()
postcss-safe-parser: undefined ()
postcss-scss: undefined ()
postcss-value-parser: undefined ()
process: undefined ()
punycode: undefined ()
querystring-es3: undefined ()
raw-body: undefined ()
react: ^18.3.1 => 18.3.1
react-builtin: undefined ()
react-dom: ^18.3.1 => 18.3.1
react-dom-builtin: undefined ()
react-dom-experimental-builtin: undefined ()
react-experimental-builtin: undefined ()
react-is: 18.2.0
react-refresh: 0.12.0
react-server-dom-turbopack-builtin: undefined ()
react-server-dom-turbopack-experimental-builtin: undefined ()
react-server-dom-webpack-builtin: undefined ()
react-server-dom-webpack-experimental-builtin: undefined ()
regenerator-runtime: 0.13.4
sass-loader: undefined ()
scheduler-builtin: undefined ()
scheduler-experimental-builtin: undefined ()
schema-utils: undefined ()
semver: undefined ()
send: undefined ()
server-only: 0.0.1
setimmediate: undefined ()
shell-quote: undefined ()
source-map: undefined ()
source-map08: undefined ()
stacktrace-parser: undefined ()
stream-browserify: undefined ()
stream-http: undefined ()
string-hash: undefined ()
string_decoder: undefined ()
strip-ansi: undefined ()
superstruct: undefined ()
tailwindcss: ^3.4.3 => 3.4.3
tar: undefined ()
terser: undefined ()
text-table: undefined ()
timers-browserify: undefined ()
tsx: ^4.9.0 => 4.9.0
tty-browserify: undefined ()
typescript: ^5.4.5 => 5.4.5 (4.4.4, 4.9.5)
ua-parser-js: undefined ()
unistore: undefined ()
util: undefined ()
vm-browserify: undefined ()
watchpack: undefined ()
web-vitals: undefined ()
webpack: undefined ()
webpack-sources: undefined ()
ws: undefined ()
zod: undefined ()
npmGlobalPackages:
@aws-amplify/cli: 12.10.3
corepack: 0.22.0
create-next-app: 14.2.3
npm: 10.2.4
Describe the bug
Consider the use case in which a lambda function needs to use a custom mutation. Before v6.2 we could use allow.authenticated('iam'), however since the upgrade to v6.2 it doesn't work anymore.
Neither does the .authorization((allow) => [ allow.resource(...) ]) applied to the whole schema, this only works for non-custom queries and mutations.
Expected behavior
An authorization mechanism to authorize a lambda function to execute a custom mutation should exist
Reproduction steps
Define the schema:
const schema = a.schema({
Impression: a.model({
videoId: a.string().required(),
impressions: a.integer().default(0)
}).identifier(['videoId'])
.authorization((allow) => [
allow.publicApiKey(),
allow.authenticated()
]),
//Executes atomic increment operation on the impressions field of the Impression model
increaseImpression: a
.mutation()
.arguments({
videoId: a.string(),
count: a.integer()
})
.returns(a.ref('Impression'))
.authorization((allow) => [
allow.authenticated()
])
.handler(a.handler.custom({
dataSource: a.ref('Impression'),
entry: './increment-impression.js'
})),
})
.authorization((allow) => [
allow.resource(incrementImpression),
]);
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
apiKeyAuthorizationMode: {}
},
});
Define a function:
export const incrementImpression = defineFunction({
});
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { env } from '@env/increment-impression';
import { modelIntrospection } from '../../../amplifyconfiguration.json';
import { Schema } from '../../data/resource';
Amplify.configure(
{
API: {
GraphQL: {
endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT, // replace with your defineData name
region: env.AWS_REGION,
defaultAuthMode: 'iam',
modelIntrospection: modelIntrospection as never
}
}
},
{
Auth: {
credentialsProvider: {
getCredentialsAndIdentityId: async () => ({
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
sessionToken: env.AWS_SESSION_TOKEN,
},
}),
clearCredentialsAndIdentityId: () => {
/* noop */
},
},
},
}
);
const dataClient = generateClient<Schema>();
export const handler = async (event: any) => {
const { data, errors } = await dataClient.mutations.increaseImpression({ videoId:'', count:10 }, { authMode: 'iam' });
if (errors) {
console.log('errors: ', errors);
}
console.log('data: ', data);
}
This results in the following error:
errors: [ { path: [ 'increaseImpression' ], data: null, errorType: 'Unauthorized', errorInfo: null, locations: [ [Object] ], message: 'Not Authorized to access increaseImpression on type Mutation' } ]
Same problem can be seen by running the mutation from the AppSync console:
Unless the schema is manually edited and the
@aws_iam is added to the increaseImpression declaration - then it works!
Code Snippet
// Put your code below this line.
Log output
// Put your logs below this line
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response
Hi @gpavlov2016 can you try changing your mutations handler from custom to function?
ex:
const schema = a.schema({
Impression: a
.model({
videoId: a.string().required(),
impressions: a.integer().default(0),
})
.identifier(["videoId"])
.authorization((allow) => [allow.publicApiKey(), allow.authenticated()]),
//Executes atomic increment operation on the impressions field of the Impression model
increaseImpression: a
.mutation()
.arguments({
videoId: a.string(),
count: a.integer(),
})
.returns(a.ref("Impression"))
.authorization((allow) => [allow.authenticated()])
.handler(a.handler.function(incrementImpression)),
});
Mutation performed from the AppSync console with IAM:
Although, I am a little confused about the shared code. Your mutation is using the Lambda as the handler but the handler's logic is also invoking the mutation.
So, the mutation is invoking itself? Is that intentional?
Apologies for the confusion, there are two different functions, I probably should have picked better names for them. Let me try to explain the situation:
- I need to call the increment impression custom mutation from Android
- Android Amplify framework currently doesn't support custom mutations issue open here
- As a solution I created a lambda function (increaseImpression) that can be called through a REST API endpoint that in turn will call the custom increment impression mutation.
- I used IAM authentication to authorize the Lambda to execute the mutation.
- With the 6.2 update the IAM authentication has been removed so I am back to square one - call increment impression custom mutation from Android doesn't work.
So it's not really the mutation handler that I need to authorize but an external function to invoke that handler through GraphQL.
One of the ideas that I am exploring based on your suggestion is to use a function handler instead of custom resolver for the custom mutation implementation but unfortunately the documentation omits the example for this use case and instead shows how to implement a query handler that doesn't include accessing the DB. Link to documentation
Oh okay, I see. In that case, the schema level allow.resources should suffice 🤔
I'll try to reproduce again with an external lambda that is separate from the handler
@chrisbonifacio hi, in the docs i can see a reference to allow.resource however this isn't included in the latest amplify modules - please advise?
System: OS: macOS 14.5 CPU: (12) arm64 Apple M2 Pro Memory: 153.28 MB / 32.00 GB Shell: /bin/zsh Binaries: Node: 20.9.0 - ~/.nvm/versions/node/v20.9.0/bin/node Yarn: undefined - undefined npm: 10.1.0 - ~/.nvm/versions/node/v20.9.0/bin/npm pnpm: undefined - undefined NPM Packages: @aws-amplify/backend: 1.0.2 @aws-amplify/backend-cli: 1.0.3 aws-amplify: 6.3.3 aws-cdk: 2.140.0 aws-cdk-lib: 2.140.0 typescript: 5.4.5 AWS environment variables: AWS_STS_REGIONAL_ENDPOINTS = regional AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1 AWS_SDK_LOAD_CONFIG = 1 No CDK environment variables
Hi @domthomas1 can you share your schema where you're trying to call allow.resource()?
Hi @chrisbonifacio, an example schema below. The differences to the docs that I can see are that I am referencing a separate function rather than defining it from within the data resources file which contains the schema, plus I am linking the authorization to the model directly rather than the schema (I have a number of models with different auth why at that level).
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
import { exampleHandler } from '../functions/example-handler/resource';
const schema = a.schema({
ExampleA: a
.model({
name: a.string(),
description: a.string()
})
.authorization(allow => [allow.owner().to(['create', 'update', 'list'])]),
ExampleB: a
.model({
name: a.string(),
description: a.string()
})
.authorization(allow => [allow.resource(exampleHandler)])
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool'
}
});
Hi @gpavlov2016 Apologies for the delay. Are you still experiencing this issue?
Looking at the schema you shared, it seems you are using allow.resource on a model. This is not supported. The way to grant a Lambda access to the API (queries/mutations/subscriptions) is to set an authorization rule on the schema itself like so:
const functionWithDataAccess = defineFunction({
entry: '../functions/data-access.ts'
});
const schema = a
.schema({
Todo: a.model({
name: a.string(),
description: a.string()
})
})
.authorization(allow => [allow.resource(functionWithDataAccess)]);
Alternatively, you can also grant access to graphql query/mutation/subscriptions separately like so:
const myLambda = defineFunction({
entry: "./handler.ts",
});
const backend = defineBackend({
auth,
data,
myLambda,
});
backend.data.resources.graphqlApi.grantMutation(
backend.myLambda.resources.lambda
);
Please refer to: https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/
Hi @chrisbonifacio, thanks for your response, the first approach works with my data schema (no typescript errors)
Hi @gpavlov2016 I found an away on AWS amplify discord group.
const ddbProductDataSourceRoleArn = backend.data.resources.cfnResources.cfnDataSources["ProductTable"].serviceRoleArn
const ddbProductTable = backend.data.resources.tables["Product"]
if (ddbProductTable) {
const role = Role.fromRoleArn(Stack.of(backend.data), 'DynamoDBServiceRoleArn', ddbProductDataSourceRoleArn)
role.addToPrincipalPolicy(new PolicyStatement({
actions: [
"dynamodb:PutItem",
"dynamodb:BatchGetItem"
],
resources: [
ddbProductTable.tableArn
],
}))
}
But it seems work around.
I think a better solution is use lambda handler functions for now.
I am having trouble adding a custom JS resolver to my schema. When I try to use it, it throws errors. Here is my schema:
const schema = a.schema({
RoomMember: a
.model({
userId: a.string().required(),
roomId: a.string().required(),
roomName: a.string(),
userFullName: a.string().required(),
user: a.belongsTo('User', 'userId'),
room: a.belongsTo('Room', 'roomId'),
})
.secondaryIndexes((index) => [
index('userId').sortKeys(['roomId']),
index('roomId').sortKeys(['userFullName']),
index('roomId').sortKeys(['roomName']),
])
.authorization((allow) => [allow.publicApiKey()]),
User: a
.model({
fullName: a.string().required(),
avatarUrl: a.string(),
users: a.hasMany('RoomMember', 'userId'),
})
.authorization((allow) => [allow.publicApiKey()]),
Room: a
.model({
roomName: a.string(),
ownerId: a.string().required(),
members: a.hasMany('RoomMember', 'roomId'),
messages: a.hasMany('Message', 'roomId'),
})
.authorization((allow) => [allow.publicApiKey()]),
Message: a
.model({
text: a.string(),
attachmentUrl: a.string(),
userId: a.string().required(),
roomId: a.string().required(),
room: a.belongsTo('Room', 'roomId'),
})
.secondaryIndexes((index) => [index('roomId').sortKeys(['text'])])
.authorization((allow) => [allow.publicApiKey()]),
batchAddUsers: a
.mutation()
.arguments({
users: a.string().array().required(),
})
.returns(a.string())
.authorization((allow) => [allow.publicApiKey()])
.handler(
a.handler.custom({
dataSource: a.ref('User'),
entry: './resolvers/batchCreateUsers.resolver.js',
}),
),
batchAddRoomMembers: a
.mutation()
.arguments({
roomId: a.string().required(),
users: a.string().array().required(),
})
.returns(a.string())
.authorization((allow) => [allow.publicApiKey()])
.handler(
a.handler.custom({
dataSource: a.ref('RoomMember'),
entry: './resolvers/batchCreateRoomMember.resolver.js',
}),
),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
apiKeyAuthorizationMode: { expiresInDays: 7 },
},
});
Here is the error:
:"{ "data": { "batchAddUsers": null }, "errors": [ { "path": [ "batchAddUsers" ], "data": null, "errorType": "DynamoDB:DynamoDbException", "errorInfo": null, "locations": [ { "line": 6, "column": 3, "sourceName": null } ], "message": "User: arn:aws:sts::339712945914:assumed-role/UserIAMRole0e5381-ztkmmcwfabel5d3zhzduxwnbgq-NONE/APPSYNC_ASSUME_ROLE is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:us-east-1:339712945914:table/User because no identity-based policy allows the dynamodb:BatchWriteItem action (Service: DynamoDb, Status Code: 400, Request ID: 5H0C1D2HF8P6E79U0FIO9708KJVV4KQNSO5AEMVJF66Q9ASUAAJG)" } ] }