Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Multiple Authorization Types with an AWS AppSync Endpoint #1

Closed
itrestian opened this issue Feb 24, 2019 · 42 comments

Comments

@itrestian
Copy link

commented Feb 24, 2019

Multiple Authorization Types with an AWS AppSync Endpoint

Currently, AWS AppSync endpoints support 4 types of authorization: AWS_IAM, AMAZON_COGNITO_USER_POOLS, API_KEY, and OPENID_CONNECT. The 4 options are mutually exclusive meaning they can't be mixed inside the same AWS AppSync API.

We heard multiple times that customers want to mix authorization types with their AWS AppSync APIs and this proposal is seeking feedback on our current vision for doing so. Some of the most common scenarios we have heard customers ask for are the following:

  • Customers want to use Cognito User Pools/OIDC tokens to read/write/subscribe from mobile or web clients but also have back-end processes that need GraphQL access as well - for instance a Local Resolver doing a mutation from a Lambda function, or a CI/CD process doing another data plane modification. So one use case would be to mix Cognito User Pools authorization with AWS IAM authorization.
  • Customers want a single AWS AppSync API for both Private and Public data coming from their API. The preferred way to do this would be to use API_KEY for Public(unauthenticated) access and IAM/Cognito User Pools/OIDC for Private(Authenticated) access.

With this proposal, users will have the ability to add additional authorization types to their AWS AppSync APIs through the console and the CLI.

We propose that, at the schema level, different authorization types can be specified using directives on the schema. Users can specify authorization modes on fields in their schema. For example, for API_KEY authorization one would use @aws_api_key on schema types/fields.

We propose that we will maintain the existing setting with regards to authorization at the AWS AppSync API level. This setting will act as the default on the schema meaning that any type that doesn't have a specific directive will have to pass the API level authorization setting same as before so existing AWS AppSync APIs and schema will act exactly as before if users don't make any changes to their settings and schema. As mentioned before, developers will have access to directives only by adding additional authorization methods to their AWS AppSync APIs. Adding multiple Cognito User Pools/OIDC as authorization providers to your AWS AppSync API will be possible.

Example: If Cognito User Pools is currently enabled as the authorization type on a AWS AppSync API meaning all fields that don't have a directive on the schema need to pass Cognito User Pools authorization. API Key can also be enabled as another authorization provider on the AWS AppSync API and users can make a type available for API_KEY authorization by using a @aws_api_key directive against it on the schema such as below.

schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id: ID): Post
    getAllPosts(): [Post]
    @aws_api_key
}

type Mutation {
    addPost(
        id: ID!
        author: String!
        title: String!
        content: String!
        url: String!
    ): Post!
}

type Post @aws_api_key {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
}

Field-level auth with new auth directives

Auth directives can be specified on root level queries and mutations. They can also be specified on object type definitions and on individual fields. For the example above, we specify that getAllPosts can be accessed by using an Api Key. We are also specifically saying that Post can be accessed using an Api Key. Note that all the authenticated options such as AWS_IAM, Cognito User Pools, and OIDC can access Api key protected types/fields. This functions as an access control list such that if you go more specific and that in the Post for example, you restrict access to certain fields. For example we can add a restrictedContent field to the Post type and restrict access to it by using the @aws_iam directive. AWS_IAM authenticated requests would be able to access restrictedContent, however Api Key requests would not be able to access it.

type Post @aws_api_key {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    restrictedContent: String!
    @aws_iam
}
@Tixamala

This comment has been minimized.

Copy link

commented Feb 26, 2019

Hi this sounds nice and is exactly what I need. So thanks for working on this!

One question: Is it possible to define two objects (types and/or inputs) with the same name but different fields for two different ident methods? I see that I could do this with the in field declaration but it would be not that readable and more error prone then two separate objects.

E.g.

type Order {
id: ID!
userID: ID!
article: String
}

type Order @aws_api_key {
id: ID!
article: String
address: Address
}

Thanks for your amazing work and I'm looking forward for this feature.

@jgunnink

This comment has been minimized.

Copy link

commented Feb 26, 2019

Hello,

Firstly thank you for opening up the AppSync community, it's great to be able to contribute ideas and feedback to the development of the product.

Adding a +1 for the authenticated and unauthenticated requests. It's something I've come across frequently. My preference would be to have an annotation which is completely public, rather than one needing an @aws_api_key, for example:

type Post @public {
    id: ID!
    author: String
    title: String
    content: String
    restrictedContent: String!
    @aws_iam
}

Thank you for the continued development on AppSync, and like others I am looking forward to this feature.
Cheers

@joemastersemison

This comment has been minimized.

Copy link

commented Feb 26, 2019

We don't use the code generation side within Amplify (currently only using the Amplify React and React Native libraries), but would absolutely love to see API_KEY + USER POOLS as auth options for a single API, for the anonymous access issue (as right now, we have to create an "anonymous" user in the user pool, which creates issues we have to work around)..

I would just want good ways to set this up through the CLI and a coherent way to test easily which was in use in our resolvers. Ideally some way to tell that would be a fairly simple VTL check.

@buggy

This comment has been minimized.

Copy link

commented Feb 26, 2019

There's already an @aws_auth directive. Would it make more sense to extend that?

schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id: ID): Post
    getAllPosts(): [Post]
    @aws_auth(api_key: true)
}

type Mutation {
    addPost(
        id: ID!
        author: String!
        title: String!
        content: String!
        url: String!
    ): Post!
}

type Post @aws_auth(api_key: true) {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
}

Given that each authentication method is separate it would allow something like

@aws_auth(cognito_groups: ["Bloggers"], api_key: true, aws_iam: true)

This could imply Cogito users in the group Bloggers or anyone with API key authentication or anyone with IAM authentication.

Could there be an option to set the default to ALLOW/DENY for each authentication method similar to how Cognito User Pools does it?

Example: A GraphQL API behind a public website. A few types/fields are only available with Cognito User Pool authentication but most types/fields are available regardless of whether an API key or Cognito User Pool token is used. With the current proposal I would need setup Cognito User Pools as the default authentication method then add @aws_api_key to a lot of types/fields. If I could set the default for both authentication methods to allow then I could add @aws_auth(api_key: false) to just the types/fields I only want people with Cognito User Pool credentials to access. I think this is more clear than seeing @aws_api_key attached to almost everything.

I'd like to know more about how you add the extra authentication methods. Will this support CloudFormation?

+1 for for true anonymous access (i.e. no API key, no AWS IAM, no authorization at all).

@bboure

This comment has been minimized.

Copy link

commented Feb 26, 2019

Thank you for this proposal. This sounds like a great idea.
Maybe it is a bit out of topic but I'd also like to see a CUSTOM authorization in AppSync.
Maybe something calling a lambda that returns JWT.

@appwiz

This comment has been minimized.

Copy link
Contributor

commented Feb 26, 2019

@bboure Could you create a new issue in this repository requesting custom authorizers with your use case?

@itrestian

This comment has been minimized.

Copy link
Author

commented Feb 27, 2019

@Tixamala You wouldn't be able to define 2 object types with the same name in the same AWS AppSync API but with this proposal, you wouldn't need to do that. We would support multiple directives on the same object type definition and in the case of an unauth/auth mix on the AWS AppSync API, you would have access to fields marked as unauthenticated by supplying an authenticated authorization method configured on your AWS AppSync API.

@jgunnink The reason for having an api key for unauthenticated access is that in the future we want to provide customers with the capability to throttle and for this we would need an identifier for customers to throttle on. However, yours is a legitimate request that we have heard in the past and I will +1 the internal feature request on your behalf.

@joemastersemison This is entirely server-side. The CLI would leverage server-side auth capabilities.

@buggy That is one of the paths we considered for this proposal. However, the @aws_auth directive currently works only on top level query and mutation types. We wanted to give users more flexibility when specifying directives such as placing them on object type definitions or individual fields within an object type definition.

@bboure I have created the following feature request in this repo on your behalf that other users can +1.

#2

@bboure

This comment has been minimized.

Copy link

commented Feb 28, 2019

Thanks @itrestian

@luillyfe

This comment has been minimized.

Copy link

commented Mar 4, 2019

@itrestian
This is awesome but I was thinking a little more in extending AMAZON_COGNITO_USER_POOLS authorization type (instead of mixin authorizations types). Let's say, I would like to define permissions for unauthenticated users via Amazon Cognito groups.

For instance, every unauthenticated user gets added to a unauthenticated group, and in that way we can define the scope of the authorization based on that group.

type Query {
    getPost(id: ID): Post
    getAllPosts(): [Post]
    @aws_auth(cognito_groups: ["unauthenticated"])
}

Let me know if this makes sense for you guys.

@itrestian

This comment has been minimized.

Copy link
Author

commented Mar 7, 2019

@luillyfe

Yes, I understand the workaround. It is similar to what JeffB@AWS is suggesting here: https://forums.aws.amazon.com/thread.jspa?messageID=841543&tstart=0

One thing to note is that this accomplishes only part of the use case (public/private mix on the same AWS AppSync API) and not the admin combined with another authentication type. Also the guest user pool user would need to have a shadow profile and a password within the user pool itself.

@luillyfe

This comment has been minimized.

Copy link

commented Mar 7, 2019

@itrestian
Not sure about profile but no need for password since you can ask for an identity using Amazon cognito, aws cognito-identity get-id. With that Id would be enough for asking authorization rights inside of AWS appsync?

@lanceharper

This comment has been minimized.

Copy link

commented Mar 7, 2019

I'm stoked that this thread exists and you all are looking to add something like this.

I think @aws_api_key is a step in the right direction, but I wonder if it is as granular as needed. Unless I'm missing something, authenticating with AppSync from a server at present requires a lot of plumbing. There doesn't seem to be any official guidance on doing so other than from this Medium post. The official documentation requires quite a few workarounds to polyfill what is needed to satisfy the aws-appsync and apollo libraries. There have been breaking changes with this approach in the past that have taken some time to figure out (e.g. use an older/non-broken version).

I ended up using Cognito user pools to programmatically create / log in "server" users with the roles I want to have access to certain mutations since I haven't found a way to do so otherwise. This allows me to have multiple points of entry from the server via different lambda functions that I can lock down with certain roles without having to drop down to IAM and all of the downsides of doing so. I can just use the access token returned from my server authenticated Cognito user in the header to make the requests I need. This approach also allows me to have some semblance of "unauthenticated" users as well.

If only one API key is supported, I don't know that this would address the types of role based access on the server side I would want.

Hope this helps!

@itrestian

This comment has been minimized.

Copy link
Author

commented Mar 8, 2019

@luillyfe get-id is a Cognito Federated Identities call, not a Cognito User Pools call. Yes, it would get you an unauthenticated identity in case you don't have a login from an authorized provider but you would need to have AWS_IAM enabled on your AppSync API, not AMAZON_COGNITO_USER_POOLS authorization since currently AWS_IAM and AMAZON_COGNITO_USER_POOLS are mutually exclusive on the same AWS AppSync API. Both have workarounds for achieving the unauthenticated/authenticated use case which are better explained in the post I linked.

@lanceharper This proposal is not meant to address the RBAC use case that you brought up (totally valid use case) but mostly to allow mixing authorization providers on the same AWS AppSync API. What you are currently doing today would still be possible under this proposal. Not sure if this is what you meant but multiple API keys would be supported on the AWS AppSync API.

Can you expand more on the challenges you are facing? Do you mean you don't have the SDK support to access the AppSync data plane server-side or are these challenges mostly in authenticating with Cognito from a server environment?

@lanceharper

This comment has been minimized.

Copy link

commented Mar 11, 2019

This proposal is not meant to address the RBAC use case that you brought up (totally valid use case)

I hear that but if the proposed approach is to remove the mutual exclusivity of authorization types, I don't know that merely allowing the current API_KEY to co-exist with user pools would help my particular use cases that much.

Not sure if this is what you meant but multiple API keys would be supported on the AWS AppSync API.

If the current proposal to just support the authenticated vs unauthenticated use case, I think it is a step in the right direction, but I would probably not find it granular enough for a lot of use cases and would still require workarounds like using system generated users in Cognito. Would API Keys last more than seven days?

Can you expand more on the challenges you are facing?

I'd like to be specific when it comes to the ability to trigger certain mutations (or even surfacing that they exist). I know that some of the issue is within the GraphQL spec itself. But I'd like to make it easier than it is now to have specific lambdas call specific mutations while still being able to use Cognito to manage users of the application. The AppSync library can be used in node to sign for IAM but that limits me to using Node if I want out of the box support and makes it challenging/impossible(?) to use user pools.

My ideal scenario is a straightforward approach for using lambdas written in a non-Node language (e.g. F#) to trigger a mutation on AppSync such that I can easily leverage the subscriptions I've associated with it. I haven't seen any documentation or other examples other than the blog post I referenced above so my current workaround of using (fake) Cognito users has been the only workable solution I've found so far. Maybe I'm missing an easier approach though.

@bboure

This comment has been minimized.

Copy link

commented Mar 18, 2019

I'd like like to +1 what @luillyfe suggested. This is probably a feature request for the Cognito team though.
The idea is to allow any user to open your app and start using it without having to log in at all. A guest (temporary) user would be generated and inserted into a Cognito User Pool. You could then use that temporary user to do AppSync calls as usual. Each guest has his own user id that uniquely identifies him.
If the user signs-up later, then the "guest" user would be transformed into a real user.

I guess that a workaround, for now, would be to programmatically generate a fake user, with a fake username and password and then update it if the user decides to sign-in. That is not an ideal solution though.

Basically, what I suggest is something similar to guests in Identity pools, but for User Pools.

@itrestian

This comment has been minimized.

Copy link
Author

commented Mar 20, 2019

@lanceharper 7 days is the default for API Keys, the maximum is 1 year: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-apikey.html

Thank you, I understand your use case better at this point and providing better support when using AppSync from Lambda together with better examples is on our roadmap.

@bboure Yes, that is a feature request on the Cognito team that I will pass along.

@michaelansel

This comment has been minimized.

Copy link

commented Mar 21, 2019

Can you clarify the permission model a little more? I'm confused on when adding a directive will expand access vs restrict access.

The first example of the proposal shows that adding @aws_api_key to a Query or a type will expand permissions, but then the second example shows that adding @aws_iam to a field will restrict permissions (with an additional comment about "all the authenticated options" always having access).

It is very important to me that I can quickly reason about access grants when reviewing a schema. Thank you!

@clearly

This comment has been minimized.

Copy link

commented Mar 22, 2019

@aws_auth(cognito_groups: ["Bloggers"], api_key: true, aws_iam: true )

But I would extend anonymous explicitly

@aws_auth(cognito_groups: ["Bloggers"], api_key: true, aws_iam: true, anonymous: true)

I am in agreement with @buggy . I think extending the existing auth directive makes the most sense. And because Anonymous access is a huge issue it seems like making it a first class auth type makes sense.

@itrestian

This comment has been minimized.

Copy link
Author

commented Mar 24, 2019

@michaelansel Just to explain better, what we are proposing is to have a default authentication type on the schema corresponding to the current authentication type on the GraphQL API. If you don't make any modifications to the schema or API settings, everything will work exactly the same way as it currently does.

With this proposal, users would have the chance to add additional authentication providers as settings on their GraphQL API. These would be similar in shape to the existing settings that you can pass as authentication settings. Just adding additional authentication providers would still not have any effect on your GraphQL API because you haven't annotated your schema with directives. There are 2 places that you will be able to place directives: on fields inside a type and on object type definitions.

Just to give an example of what happens when users are trying to query their AWS AppSync endpoint. Suppose your existing authenticationType setting is AWS_IAM and you have the schema below. You add an additional authentication provider of type OPENID_CONNECT. That would mean that if the request to your AppSync API endpoint passes AWS_IAM authorization, you would be able to access getPost and not getAllPosts. Similarly, if your request passes OPENID_CONNECT authorization against the specific provider you have configured, you would be able to access getAllPosts and not getPost. This is because AWS_IAM acts as the default provider on the AppSync API.

type Query {
    getPost(id: ID): Post
    getAllPosts(): [Post]
    @openid_connect
}

You can also specify directives on object type definitions to secure them. For example, you can specify that the type Post is accessible both from an OPENID_CONNECT authorized request and from an AWS_IAM authorized request. Not specifying any directive means Post is only accessible using the default authentication type on the AppSync API.

type Post @openid_connect
                @aws_iam {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    restrictedContent: String!
}

If you want to further restrict the restrictedContent field for example to only AWS_IAM authorization you can do it in this manner:

type Post @openid_connect
                @aws_iam {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    restrictedContent: String!
   @aws_iam 
}

Now all fields inside Post are accessible for a request that is OPENID_CONNECT authorized except restrictedContent which is only available for a request that is AWS_IAM authorized.

Hope this explains it better.

@clearly Thank you for the feedback. I will pass it on to the team.

@jpmartin2

This comment has been minimized.

Copy link

commented Mar 27, 2019

@itrestian With this proposal, could you set multiple default authorizers? For one of the AppSync APIs I work with it would be great to allow clients authorized via both IAM and Cognito UserPools, but I wouldn't want to have to specify that for almost every single type in my model.

@itrestian

This comment has been minimized.

Copy link
Author

commented Apr 2, 2019

@LordPython That's a good point, I will bring it up with the team.

@anarerdene

This comment has been minimized.

Copy link

commented Apr 11, 2019

@itrestian really need this feature. This feature can fix lots of problems.

@D2KX

This comment has been minimized.

Copy link

commented Apr 12, 2019

Yeah, API + Cognito User Pools (and especially more than one) would be incredibly helpful for us aswell. Hopefully it'll be implemented relatively soon! <3

@lukasf98

This comment has been minimized.

Copy link

commented Apr 28, 2019

I'm also looking forward to having this feature. I'm currently trying to change authentication from Amazon Cognito User Pool to IAM because I want to call AppSync from a lambda function but since I need the Cognito Sub for some of the calls it is rather difficult.

@itrestian can you give a prediction for when this feature will be available?

@ITJen

This comment has been minimized.

Copy link

commented Apr 29, 2019

This is slightly off topic but will AppSync add Lambda authorizer (custom authorizer) to authorization types as for API Gateway?

@appwiz

This comment has been minimized.

Copy link
Contributor

commented Apr 29, 2019

@ITJen we are considering adding custom authorizers. Could you vote up and add your use case to #2 please?

@ITJen

This comment has been minimized.

Copy link

commented Apr 29, 2019

I will @appwiz ! I am still new to the project and don't have the details for our authentication flow. We are currently using a custom authorizer in API Gateway and we are investigating in possibilities to migrate to AppSync.

@admirkadriu

This comment has been minimized.

Copy link

commented May 2, 2019

Does additional authorization types support cloudformation?

@itrestian

This comment has been minimized.

Copy link
Author

commented May 2, 2019

Yes, cloud formation support will be available.

@appwiz appwiz added this to In progress in AWS AppSync Features May 3, 2019

@achutkiran

This comment has been minimized.

Copy link

commented May 7, 2019

Hi, For amplify this pull request aims to solve multi auth for appsync.
aws-amplify/amplify-js#3122

@ggriffin

This comment has been minimized.

Copy link

commented May 8, 2019

Perhaps having multi auth would simplify the process of triggering AppSync mutations over AWS_IAM from Lambda, thereby causing subscription updates to the front end, authenticated with AMAZON_COGNITO_USER_POOLS. I'm currently using the method where AWS_IAM is used for both front/back ends, pass the idToken/JWT via a mutation from the front end at appropriate times, verify the token via a Lambda, and then persist the claims I'm interested in.

This assumes the front end would have access to the subscription using AMAZON_COGNITO_USER_POOLS, associated with the mutation that is being triggered over AWS_IAM. I think this would mean that only clients authenticated over AWS_IAM could trigger the mutation. This should work for the use cases we have so far.

This also assumes we can leverage Social Identity Providers with AppSync while auth type is set to AMAZON_COGNITO_USER_POOLS from the front end, and I won't be forced to use AWS_IAM anyway once we get around to adding social logins.

Perhaps having Lambdas use a dummy Cognito user to cause mutations is a valid approach, but seems less correct to me.

@D2KX

This comment has been minimized.

Copy link

commented May 13, 2019

I don't think it's officially announced yet, but this feature just got enabled for us in the AppSync console :) The aws-sdk-js seems ready aswell.

@appwiz

This comment has been minimized.

Copy link
Contributor

commented May 13, 2019

@D2KX, @itrestian is hard at work rolling it out to all regions.

@appwiz appwiz moved this from In progress to Done in AWS AppSync Features May 15, 2019

@appwiz appwiz moved this from Done to In progress in AWS AppSync Features May 15, 2019

@Zerquix18

This comment has been minimized.

Copy link

commented May 15, 2019

Seems to have been rolled out! The documentation is also available. https://docs.aws.amazon.com/appsync/latest/devguide/security.html#using-additional-authorization-modes

The groups for cognito can also be secured using @aws_auth https://docs.aws.amazon.com/appsync/latest/devguide/security.html#amazon-cognito-user-pools-authorization

Happy coding!

@appwiz

This comment has been minimized.

Copy link
Contributor

commented May 15, 2019

@itrestian

This comment has been minimized.

Copy link
Author

commented May 15, 2019

@itrestian itrestian closed this May 15, 2019

AWS AppSync Features automation moved this from In progress to Done May 15, 2019

@erezrokah

This comment has been minimized.

Copy link

commented May 16, 2019

Any information on how to configure it using CloudFormation?
I couldn't find anything in the docs:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html

@D2KX

This comment has been minimized.

Copy link

commented May 16, 2019

Is CLI support for the GraphQL Transformer (@auth directive for multiple auth methods, or whatever the equivalent of @aws_iam etc. from the console will be) till forthcoming? Or am I just blind :)

@erezrokah

This comment has been minimized.

Copy link

commented May 16, 2019

Is CLI support for the GraphQL Transformer (@auth directive for multiple auth methods, or whatever the equivalent of @aws_iam etc. from the console will be) till forthcoming? Or am I just blind :)

The cli has a new --additional-authentication-providers flag:
https://docs.aws.amazon.com/cli/latest/reference/appsync/create-graphql-api.html

If that is what you're looking for

@undefobj

This comment has been minimized.

Copy link

commented May 16, 2019

@D2KX @erezrokah we'll be adding multi-auth features to the Amplify CLI in the coming month(s). Please inquire additionally in that repo for functionality questions.

@itrestian

This comment has been minimized.

Copy link
Author

commented May 16, 2019

@erezrokah CloudFormation docs will be out pretty soon.

Here's an example on how to configure AdditionalAuhenticationProviders using CloudFormation on your GraphQLApi:

  BasicGraphQLApi:
    Type: "AWS::AppSync::GraphQLApi"
    Properties:
      Name: "TestAPI with IAM and a NONE datasource with tags added."
      AuthenticationType: "AWS_IAM"
      Tags:
        -
          Key: "Group"
          Value: "1"
        -
          Key: "Colour"
          Value: ""
      AdditionalAuthenticationProviders:
        -
          AuthenticationType: "API_KEY"
        -
          AuthenticationType: "OPENID_CONNECT"
          "OpenIDConnectConfig":
            Issuer: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxjH4nUPf"
            IatTTL: 21600000
            AuthTTL: 21600000
        -
          AuthenticationType: "AMAZON_COGNITO_USER_POOLS"
          "UserPoolConfig":
            UserPoolId: "us-east-1_xxjH4nUPf"
            AwsRegion: "us-east-1"

@erezrokah

This comment has been minimized.

Copy link

commented May 16, 2019

Thanks @itrestian I figured it out by following the cli naming conventions :)
See here:
sid88in/serverless-appsync-plugin#242

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.