Open loganpowell opened 2 years ago
Hey @loganpowell :wave: thanks for raising this! I've transferred this to our API repo for better assistance 🙂
Thank you, @josefaidt!
Hey @loganpowell what is the use case you're looking to accomplish? We can leverage the AWS_IAM
auth mode to authenticate with public IAM for guest users:
import { API } from 'aws-amplify';
import * as queries from './graphql/queries';
const todos = await API.graphql({
query: queries.listTodos,
authMode: 'AWS_IAM'
});
ref https://docs.amplify.aws/lib/graphqlapi/query-data/q/platform/js/#custom-authorization-mode
As a follow-up, can you clarify this line?
Is there a way for me to add all guests (non-authenticated) to a default group in the IAM policy that's leveraged by the public auth in Amplify? ... with "public" being able to be toggled on/off by adding/removing them from the readerGroups.
Are you aiming to toggle what guests can view this data?
Hi @josefaidt! Thank you for following up.
Are you aiming to toggle what guests can view this data?
Sorry for the confusion, but no. Let me explain..
I am aware that I can set the authMode: "AWS_IAM"
, but that doesn't change the @auth
permissions set via the schema. I am trying to use dynamic groups to control access to data.
what is the use case you're looking to accomplish?
I am building a CMS, which will have a Post
that can be in a variety of different permission statuses:
I want the owner to be able to set the permissions dynamically, when the post is ready for publication. I'm thinking by using groupsField
in a post's @auth
rule, the owner can add/remove groups (including the public group) on demand.
RE:
As a follow-up, can you clarify this line?
Is there a way for me to add all guests (non-authenticated) to a default group in the IAM policy that's leveraged by the public auth in Amplify? ... with "public" being able to be toggled on/off by adding/removing them from the readerGroups.
I can't set the permissions statically on the table because that won't accomplish these aims. I have to use some kind of dynamic authorization setting and I figure dynamic groups is the only way currently afforded by Amplify to do so. Please correct me if I'm mistaken.
As a follow up, I found a blog post that goes into some detail about how the author implemented "owner" privileges to guests.
In AppSync, the resolvers' identity context from an unauthenticated Cognito identity (i.e., IAM) has the following shape:
// unauthenticated
{
"accountId" : "string",
"cognitoIdentityPoolId" : "string",
"cognitoIdentityId" : "string",
"sourceIp" : ["string"],
"username" : "string", // IAM user principal
"userArn" : "string",
"cognitoIdentityAuthType" : "string", // authenticated/unauthenticated based on the identity type
"cognitoIdentityAuthProvider" : "string" // the auth provider that was used to obtain the credentials
}
Whereas a request from an authenticated Cognito identity looks like this:
// authenticated
{
"sub" : "uuid",
"issuer" : "string",
"username" : "string",
"claims" : { ... }, // <- this is where cognito:groups lives
"sourceIp" : ["x.x.x.x"],
"defaultAuthStrategy" : "string"
}
by adjusting the auth phase in the resolvers output via Amplify - in the authors case - like so:
#set( $isAuthorized = false )
#set( $identityId = "")
#if( $util.authType() == "IAM Authorization" )
+ #set( $identityId = $ctx.identity.cognitoIdentityId )
#elseif( $util.authType() == "User Pool Authorization" )
#set( $identityId = $ctx.identity.username )
#end
#if( $identityId != $ctx.result.identityId )
$util.unauthorized()
#end
The author toggles between cognitoIdentityId
and username
depending on the auth type.
The author uses a set of Node.js scripts to post-process VTL templates in order to automate this replacement across resolvers.
In my case, I have something like this in my resolver context:
#if( $util.authType() == "IAM Authorization" )
#set( $adminRoles = ["COPECognitoPostConfirmTriggerAddToViewers-migration"] )
#foreach( $adminRole in $adminRoles )
#if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )
#return($util.toJson({}))
#end
#end
$util.unauthorized()
#end
#if( $util.authType() == "User Pool Authorization" )
#if( !$isAuthorized )
#set( $staticGroupRoles = [{"claim":"cognito:groups","entity":"Admins","allowedFields":[]}] )
#foreach( $groupRole in $staticGroupRoles )
#set( $groupsInToken = $util.defaultIfNull($ctx.identity.claims.get($groupRole.claim), []) )
#if( $groupsInToken.contains($groupRole.entity) )
#if( !$groupRole.allowedFields.isEmpty() )
$util.qr($allowedFields.addAll($groupRole.allowedFields))
#else
#set( $isAuthorized = true )
#break
#end
#end
#end
I'm not sure where I could put a similar logic to grant all IAM users a group claim (e.g., Public
).
Hey @loganpowell :wave: thank you for clarifying and including those details with the use case! And apologies for the delay here -- I've created two additional feature requests as I worked through the reproduction for this issue! That was certainly helpful! If I understand correctly it may be best to leverage two separate Lambda functions as a custom query resolver to return only the "reviewable" and "published" posts:
draft
when the record is created and the owner does not have access to modify this field@default
directive to automatically create the Post in draft
state but owner does not have permissions to set their post to any other status like review
and publish
enum PostStatus {
draft
review
published
}
type Post @model @auth(rules: [
{ allow: owner, operations: [create, read, update, delete] },
{ allow: private, provider: iam }
]) {
id: ID! @primaryKey
status: PostStatus @auth(rules: [{ allow: owner, operations: [read] }, { allow: private, provider: iam }])
}
type Query {
listPublishedPosts: [Post]! @auth(rules: [{ allow: public }]) @function(name: "listPublishedPosts-${env}")
listReviewablePosts: [Post]! @auth(rules: [{ allow: groups, groups: ["reviewers"] }]) @function(name: "listReviewablePosts-${env}")
}
With this owners will be able to CRUD their own Posts, and instead of filtering client-side and risking exposing unpublished Posts (with public IAM or API Key auth) we can use the Lambda resolvers to call the filtered query using IAM auth:
query LIST_PUBLISHED_POSTS {
listPublishedPosts(filter: {
status: {
eq: published
}
}) {
id
status
}
}
From here, calling listPublishedPosts
client-side will use API key auth to retrieve the public data. And calling listReviewablePosts
client-side will similarly check to see if the user is a member of the reviewers
group.
As a final note this example does not also cover the functionality of changing the post status, however for the sake of this example I have already included the private IAM auth rule to mitigate the owners' ability to change their own post from draft
straight to published
. Please note this is a sample schema and does not quite cover all use cases such as leveraging user groups to update the Post's status
The author uses a set of Node.js scripts to post-process VTL templates in order to automate this replacement across resolvers.
I would error on the side of caution with post-processing the VTL templates in favor of a Lambda resolver or custom VTL resolver. This way we mitigate the risk of these VTL templates changing over time as additional features or bug fixes are released and potentially breaking the post-processing script.
Finally, I think this would make a great feature request to have a conditional auth rule based on a value within the model such as:
@auth(rules: [{ allow: public, where: { status: { eq: published } } }])
Hi @josefaidt sorry for the delayed response 🙇 I've been AFK for a long time (on vacation) and didn't see this until now (catching up on a backlog). I have to look deeper into your response, but I wanted to confirm I really appreciate it! Let me parse through this please...
Ok, did some reading/thinking
As a final note this example does not also cover the functionality of changing the post status, however for the sake of this example I have already included the private IAM auth rule to mitigate the owners' ability to change their own post from draft straight to published. Please note this is a sample schema and does not quite cover all use cases such as leveraging user groups to update the Post's status
I think your proposal is getting to the core of the big idea - dynamic privileges. I've made a comment on your referenced feature request and think it's brilliant.
I would error on the side of caution with post-processing the VTL templates in favor of a Lambda resolver or custom VTL resolver. This way we mitigate the risk of these VTL templates changing over time as additional features or bug fixes are released and potentially breaking the post-processing script.
I agree. Not only this, but also VTL is not so user friendly and thus a bit out of range for the average target Amplify user IMHO. EDIT: I also have reservations about Lambda custom resolvers due to the potential bottleneck WRT concurrency if a lot of resolvers are going to be triggered on every GraphQL call.
Which Category is your question related to? Auth, API(GraphQL)
Amplify CLI Version 7.6.22
What AWS Services are you utilizing?
Provide additional details e.g. code snippets. Be sure to remove any sensitive data.
👋
Hello! I have been looking high and low for an answer to this question, but couldn't find anything in either open or closed issues here, SO, or the official Amplify docs:
I am trying to use dynamic group authorization for one of my
@model
s and can't figure out how to populate thepublic... provider: iam
with a default group assignment to end up something like this:when augmenting
Amplify.configure()
Object with:Is there a way for me to add all guests (non-authenticated) to a default group in the IAM policy that's leveraged by the
public
auth in Amplify?I want to do something like this in my schema.graphql
with
"public"
being able to be toggled on/off by adding/removing them from thereaderGroups
.Help would be greatly appreciated!
🙏