From 1fa04e1cc3b77ebe279658d3e1be92c95bdd4be1 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Thu, 17 Oct 2024 15:06:26 -0600 Subject: [PATCH 1/5] feat: add metrics metadata for auth mode and custom operations --- packages/amplify-graphql-api-construct/.jsii | 22 +- .../__tests__/__functional__/authorizer.ts | 3 + .../__tests__/__functional__/metadata.test.ts | 445 ++++++++++++++++++ .../src/amplify-graphql-api.ts | 24 +- .../src/internal/metadata.ts | 35 ++ 5 files changed, 506 insertions(+), 23 deletions(-) create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/__functional__/authorizer.ts create mode 100644 packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts create mode 100644 packages/amplify-graphql-api-construct/src/internal/metadata.ts diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 27e1c38605..ad4308e809 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -4490,7 +4490,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 292 + "line": 299 }, "name": "addDynamoDbDataSource", "parameters": [ @@ -4539,7 +4539,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 304 + "line": 311 }, "name": "addElasticsearchDataSource", "parameters": [ @@ -4586,7 +4586,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 314 + "line": 321 }, "name": "addEventBridgeDataSource", "parameters": [ @@ -4633,7 +4633,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 396 + "line": 403 }, "name": "addFunction", "parameters": [ @@ -4668,7 +4668,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 325 + "line": 332 }, "name": "addHttpDataSource", "parameters": [ @@ -4716,7 +4716,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 336 + "line": 343 }, "name": "addLambdaDataSource", "parameters": [ @@ -4764,7 +4764,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 347 + "line": 354 }, "name": "addNoneDataSource", "parameters": [ @@ -4803,7 +4803,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 358 + "line": 365 }, "name": "addOpenSearchDataSource", "parameters": [ @@ -4851,7 +4851,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 371 + "line": 378 }, "name": "addRdsDataSource", "parameters": [ @@ -4918,7 +4918,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 387 + "line": 394 }, "name": "addResolver", "parameters": [ @@ -8959,5 +8959,5 @@ } }, "version": "1.15.1", - "fingerprint": "ONMG7fvcOLL6e3aEIqPma1mGRBz9zrwcR4rugtL8yQ0=" + "fingerprint": "L/GeKg7k8fmB+9I3VgG3cmEWsNBlijYsctatQrwnsuU=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/authorizer.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/authorizer.ts new file mode 100644 index 0000000000..7420b06283 --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/authorizer.ts @@ -0,0 +1,3 @@ +export const handler = async ({ authorizationToken }: { authorizationToken: string }): Promise<{ isAuthorized: boolean }> => ({ + isAuthorized: authorizationToken === 'letmein', +}); diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts new file mode 100644 index 0000000000..1f2424efb9 --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts @@ -0,0 +1,445 @@ +import * as path from 'path'; +import { Duration, Stack } from 'aws-cdk-lib'; +import { ArnPrincipal, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; +import { CfnIdentityPool, UserPool } from 'aws-cdk-lib/aws-cognito'; +import { Template } from 'aws-cdk-lib/assertions'; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { mockSqlDataSourceStrategy } from '@aws-amplify/graphql-transformer-test-utils'; +import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; +import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; + +describe('metrics metadata', () => { + describe('dataSources', () => { + test('default dynamodb', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('amplify managed dynamodb', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString( + /* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + `, + { + dbType: 'DYNAMODB', + provisionStrategy: 'AMPLIFY_TABLE', + }, + ), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('mysql', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString( + /* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + description: String! + } + `, + mockSqlDataSourceStrategy({ dbType: 'MYSQL' }), + ), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "mysql", + } + `); + }); + + test('postgres', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString( + /* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + description: String! + } + `, + mockSqlDataSourceStrategy({ dbType: 'POSTGRES' }), + ), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "postgres", + } + `); + }); + + test('dynamodb, mysql, and postgres', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.combine([ + AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type TodoDynamoDB @model @auth(rules: [{ allow: public }]) { + description: String! + } + `), + AmplifyGraphqlDefinition.fromString( + /* GraphQL */ ` + type TodoMySQL @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + description: String! + } + `, + mockSqlDataSourceStrategy({ dbType: 'MYSQL' }), + ), + AmplifyGraphqlDefinition.fromString( + /* GraphQL */ ` + type TodoPostgres @model @auth(rules: [{ allow: public }]) { + id: ID! @primaryKey + description: String! + } + `, + mockSqlDataSourceStrategy({ dbType: 'POSTGRES' }), + ), + ]), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "dynamodb,mysql,postgres", + } + `); + }); + }); + + describe('authorizationModes', () => { + test('AWS_IAM', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: iam, allow: public }, { provider: iam, allow: private }]) { + description: String! + } + `), + authorizationModes: { + iamConfig: { + identityPoolId: 'abc', + unauthenticatedUserRole: new Role(stack, 'testUnauthRole', { + assumedBy: new ArnPrincipal('aws:iam::1234:root'), + }), + authenticatedUserRole: new Role(stack, 'testAuthRole', { + assumedBy: new ArnPrincipal('aws:iam::1234:root'), + }), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "AWS_IAM", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('AMAZON_COGNITO_USER_POOLS', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: owner }]) { + description: String! + } + `), + authorizationModes: { + userPoolConfig: { + userPool: new UserPool(stack, 'testUserPool'), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "AMAZON_COGNITO_USER_POOLS", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('API_KEY', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('AWS_LAMBDA', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: function, allow: custom }]) { + description: String! + } + `), + authorizationModes: { + lambdaConfig: { + function: new NodejsFunction(stack, 'TestAuthorizer', { entry: path.join(__dirname, 'authorizer.ts') }), + ttl: Duration.days(7), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "AWS_LAMBDA", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('OPENID_CONNECT', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: oidc, allow: owner }]) { + description: String! + } + `), + authorizationModes: { + oidcConfig: { + oidcProviderName: 'testProvider', + oidcIssuerUrl: 'https://test.client/', + clientId: 'testClient', + tokenExpiryFromAuth: Duration.minutes(5), + tokenExpiryFromIssue: Duration.minutes(5), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "OPENID_CONNECT", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('AMAZON_COGNITO_IDENTITY_POOLS', () => { + const stack = new Stack(); + const identityPool = new CfnIdentityPool(stack, 'TestIdentityPool', { allowUnauthenticatedIdentities: true }); + const appsync = new ServicePrincipal('appsync.amazonaws.com'); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ provider: iam, allow: public }, { provider: iam, allow: private }]) { + description: String! + } + `), + authorizationModes: { + identityPoolConfig: { + identityPoolId: identityPool.logicalId, + authenticatedUserRole: new Role(stack, 'AuthRole', { assumedBy: appsync }), + unauthenticatedUserRole: new Role(stack, 'UnauthRole', { assumedBy: appsync }), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "AMAZON_COGNITO_IDENTITY_POOLS", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + + test('multiple', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo + @model + @auth( + rules: [ + { allow: public } + { provider: iam, allow: public } + { provider: iam, allow: private } + { provider: function, allow: custom } + ] + ) { + description: String! + } + `), + authorizationModes: { + defaultAuthorizationMode: 'API_KEY', + apiKeyConfig: { expires: Duration.days(7) }, + iamConfig: { + identityPoolId: 'abc', + unauthenticatedUserRole: new Role(stack, 'testUnauthRole', { + assumedBy: new ArnPrincipal('aws:iam::1234:root'), + }), + authenticatedUserRole: new Role(stack, 'testAuthRole', { + assumedBy: new ArnPrincipal('aws:iam::1234:root'), + }), + }, + lambdaConfig: { + function: new NodejsFunction(stack, 'TestAuthorizer', { entry: path.join(__dirname, 'authorizer.ts') }), + ttl: Duration.days(7), + }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY,AWS_IAM,AWS_LAMBDA", + "customOperations": "", + "dataSources": "dynamodb", + } + `); + }); + }); + + describe('customOperations', () => { + test('queries', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + + type Query { + getCustomTodos: [Todo] + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "queries", + "dataSources": "dynamodb", + } + `); + }); + + test('mutations', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + + type Mutation { + addCustomTodo: Todo + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "mutations", + "dataSources": "dynamodb", + } + `); + }); + + test('mutations and queries', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + + type Mutation { + addCustomTodo: Todo + } + + type Query { + getCustomTodos: [Todo] + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "API_KEY", + "customOperations": "queries,mutations", + "dataSources": "dynamodb", + } + `); + }); + }); +}); diff --git a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts index 09405a4b39..2d77a15c9a 100644 --- a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts +++ b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts @@ -35,7 +35,6 @@ import type { IBackendOutputStorageStrategy, AddFunctionProps, DataStoreConfiguration, - IAmplifyGraphqlDefinition, } from './types'; import { convertAuthorizationModesToTransformerAuthConfig, @@ -50,6 +49,7 @@ import { } from './internal'; import { getStackForScope, walkAndProcessNodes } from './internal/construct-tree'; import { getDataSourceStrategiesProvider } from './internal/data-source-config'; +import { getMetadataDataSources, getMetadataAuthorizationModes, getMetadataCustomOperations } from './internal/metadata'; /** * L3 Construct which invokes the Amplify Transformer Pattern over an input Graphql Schema. @@ -168,11 +168,18 @@ export class AmplifyGraphqlApi extends Construct { this.dataStoreConfiguration = dataStoreConfiguration || conflictResolution; - const dataSources = getMetadataDataSources(definition); + const attributionMetadata = { + dataSources: getMetadataDataSources(definition), + authorizationModes: getMetadataAuthorizationModes(authorizationModes), + customOperations: getMetadataCustomOperations(definition), + }; - new AttributionMetadataStorage().storeAttributionMetadata(Stack.of(scope), this.stackType, path.join(__dirname, '..', 'package.json'), { - dataSources, - }); + new AttributionMetadataStorage().storeAttributionMetadata( + Stack.of(scope), + this.stackType, + path.join(__dirname, '..', 'package.json'), + attributionMetadata, + ); validateAuthorizationModes(authorizationModes); const { authConfig, authSynthParameters } = convertAuthorizationModesToTransformerAuthConfig(authorizationModes); @@ -420,10 +427,3 @@ const validateNoOtherAmplifyGraphqlApiInStack = (scope: Construct): void => { throw new Error('Only one AmplifyGraphqlApi is expected in a stack. Place the AmplifyGraphqlApis in separate nested stacks.'); } }; - -const getMetadataDataSources = (definition: IAmplifyGraphqlDefinition): string => { - const dataSourceDbTypes = Object.values(definition.dataSourceStrategies).map((strategy) => strategy.dbType.toLocaleLowerCase()); - const customSqlDbTypes = (definition.customSqlDataSourceStrategies ?? []).map((strategy) => strategy.strategy.dbType.toLocaleLowerCase()); - const dataSources = [...new Set([...dataSourceDbTypes, ...customSqlDbTypes])].sort(); - return dataSources.join(','); -}; diff --git a/packages/amplify-graphql-api-construct/src/internal/metadata.ts b/packages/amplify-graphql-api-construct/src/internal/metadata.ts new file mode 100644 index 0000000000..afd85f8d67 --- /dev/null +++ b/packages/amplify-graphql-api-construct/src/internal/metadata.ts @@ -0,0 +1,35 @@ +import { AuthorizationModes, IAmplifyGraphqlDefinition } from '../types'; + +export const getMetadataDataSources = (definition: IAmplifyGraphqlDefinition): string => { + const dataSourceDbTypes = Object.values(definition.dataSourceStrategies).map((strategy) => strategy.dbType.toLocaleLowerCase()); + const customSqlDbTypes = (definition.customSqlDataSourceStrategies ?? []).map((strategy) => strategy.strategy.dbType.toLocaleLowerCase()); + const dataSources = [...new Set([...dataSourceDbTypes, ...customSqlDbTypes])].sort(); + return dataSources.join(','); +}; + +export const getMetadataAuthorizationModes = (authorizationModes: AuthorizationModes): string => { + const configKeyToAuthMode: Record = { + iamConfig: 'AWS_IAM', + oidcConfig: 'OPENID_CONNECT', + identityPoolConfig: 'AMAZON_COGNITO_IDENTITY_POOLS', + userPoolConfig: 'AMAZON_COGNITO_USER_POOLS', + apiKeyConfig: 'API_KEY', + lambdaConfig: 'AWS_LAMBDA', + }; + const authModes = Object.keys(authorizationModes) + .map((mode) => configKeyToAuthMode[mode]) + .filter((mode) => !!mode); + return authModes.join(','); +}; + +export const getMetadataCustomOperations = (definition: IAmplifyGraphqlDefinition): string => { + const customOperations: string[] = []; + if (definition.schema.includes('type Query')) { + customOperations.push('queries'); + } + if (definition.schema.includes('type Mutation')) { + customOperations.push('mutations'); + } + + return customOperations.join(','); +}; From 3d6c9ce37ad8622c949526e07abb969e677ae7de Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 18 Oct 2024 08:27:56 -0600 Subject: [PATCH 2/5] docs: add clarification comment --- packages/amplify-graphql-api-construct/src/internal/metadata.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amplify-graphql-api-construct/src/internal/metadata.ts b/packages/amplify-graphql-api-construct/src/internal/metadata.ts index afd85f8d67..e881f01b92 100644 --- a/packages/amplify-graphql-api-construct/src/internal/metadata.ts +++ b/packages/amplify-graphql-api-construct/src/internal/metadata.ts @@ -18,6 +18,7 @@ export const getMetadataAuthorizationModes = (authorizationModes: AuthorizationM }; const authModes = Object.keys(authorizationModes) .map((mode) => configKeyToAuthMode[mode]) + // remove values not found in mapping .filter((mode) => !!mode); return authModes.join(','); }; From db6312462f1834d53e9ef14200efc62599fd8007 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 18 Oct 2024 10:12:54 -0600 Subject: [PATCH 3/5] fix: change to lowercase --- .../__tests__/__functional__/metadata.test.ts | 30 +++++++++---------- .../src/internal/metadata.ts | 12 ++++---- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts index 1f2424efb9..2605f43343 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts @@ -25,7 +25,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "dynamodb", } @@ -53,7 +53,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "dynamodb", } @@ -79,7 +79,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "mysql", } @@ -105,7 +105,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "postgres", } @@ -147,7 +147,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "dynamodb,mysql,postgres", } @@ -179,7 +179,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "AWS_IAM", + "authorizationModes": "aws_iam", "customOperations": "", "dataSources": "dynamodb", } @@ -203,7 +203,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "AMAZON_COGNITO_USER_POOLS", + "authorizationModes": "amazon_cognito_user_pools", "customOperations": "", "dataSources": "dynamodb", } @@ -225,7 +225,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "", "dataSources": "dynamodb", } @@ -250,7 +250,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "AWS_LAMBDA", + "authorizationModes": "aws_lambda", "customOperations": "", "dataSources": "dynamodb", } @@ -278,7 +278,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "OPENID_CONNECT", + "authorizationModes": "openid_connect", "customOperations": "", "dataSources": "dynamodb", } @@ -306,7 +306,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "AMAZON_COGNITO_IDENTITY_POOLS", + "authorizationModes": "amazon_cognito_identity_pools", "customOperations": "", "dataSources": "dynamodb", } @@ -351,7 +351,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY,AWS_IAM,AWS_LAMBDA", + "authorizationModes": "api_key,aws_iam,aws_lambda", "customOperations": "", "dataSources": "dynamodb", } @@ -379,7 +379,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "queries", "dataSources": "dynamodb", } @@ -405,7 +405,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "mutations", "dataSources": "dynamodb", } @@ -435,7 +435,7 @@ describe('metrics metadata', () => { const template = Template.fromStack(stack); expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { - "authorizationModes": "API_KEY", + "authorizationModes": "api_key", "customOperations": "queries,mutations", "dataSources": "dynamodb", } diff --git a/packages/amplify-graphql-api-construct/src/internal/metadata.ts b/packages/amplify-graphql-api-construct/src/internal/metadata.ts index e881f01b92..197156a83c 100644 --- a/packages/amplify-graphql-api-construct/src/internal/metadata.ts +++ b/packages/amplify-graphql-api-construct/src/internal/metadata.ts @@ -9,12 +9,12 @@ export const getMetadataDataSources = (definition: IAmplifyGraphqlDefinition): s export const getMetadataAuthorizationModes = (authorizationModes: AuthorizationModes): string => { const configKeyToAuthMode: Record = { - iamConfig: 'AWS_IAM', - oidcConfig: 'OPENID_CONNECT', - identityPoolConfig: 'AMAZON_COGNITO_IDENTITY_POOLS', - userPoolConfig: 'AMAZON_COGNITO_USER_POOLS', - apiKeyConfig: 'API_KEY', - lambdaConfig: 'AWS_LAMBDA', + iamConfig: 'aws_iam', + oidcConfig: 'openid_connect', + identityPoolConfig: 'amazon_cognito_identity_pools', + userPoolConfig: 'amazon_cognito_user_pools', + apiKeyConfig: 'api_key', + lambdaConfig: 'aws_lambda', }; const authModes = Object.keys(authorizationModes) .map((mode) => configKeyToAuthMode[mode]) From 42526fa39b872008284eb60599e7fd671fe42eb6 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 18 Oct 2024 11:39:08 -0600 Subject: [PATCH 4/5] feat: add custom subscriptions to metadata --- .../__tests__/__functional__/metadata.test.ts | 34 +++++++++++++++++-- .../src/internal/metadata.ts | 3 ++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts index 2605f43343..b8ba77e09e 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts @@ -412,7 +412,33 @@ describe('metrics metadata', () => { `); }); - test('mutations and queries', () => { + test('subscriptions', () => { + const stack = new Stack(); + new AmplifyGraphqlApi(stack, 'TestApi', { + definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` + type Todo @model @auth(rules: [{ allow: public }]) { + description: String! + } + + type Subscription { + onCreateCustomTodo: Todo + } + `), + authorizationModes: { + apiKeyConfig: { expires: Duration.days(7) }, + }, + }); + const template = Template.fromStack(stack); + expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` + Object { + "authorizationModes": "api_key", + "customOperations": "subscriptions", + "dataSources": "dynamodb", + } + `); + }); + + test('mutations, queries, and subscriptions', () => { const stack = new Stack(); new AmplifyGraphqlApi(stack, 'TestApi', { definition: AmplifyGraphqlDefinition.fromString(/* GraphQL */ ` @@ -427,6 +453,10 @@ describe('metrics metadata', () => { type Query { getCustomTodos: [Todo] } + + type Subscription { + onCreateCustomTodo: Todo + } `), authorizationModes: { apiKeyConfig: { expires: Duration.days(7) }, @@ -436,7 +466,7 @@ describe('metrics metadata', () => { expect(JSON.parse(template.toJSON().Description).metadata).toMatchInlineSnapshot(` Object { "authorizationModes": "api_key", - "customOperations": "queries,mutations", + "customOperations": "queries,mutations,subscriptions", "dataSources": "dynamodb", } `); diff --git a/packages/amplify-graphql-api-construct/src/internal/metadata.ts b/packages/amplify-graphql-api-construct/src/internal/metadata.ts index 197156a83c..f11a498bc2 100644 --- a/packages/amplify-graphql-api-construct/src/internal/metadata.ts +++ b/packages/amplify-graphql-api-construct/src/internal/metadata.ts @@ -31,6 +31,9 @@ export const getMetadataCustomOperations = (definition: IAmplifyGraphqlDefinitio if (definition.schema.includes('type Mutation')) { customOperations.push('mutations'); } + if (definition.schema.includes('type Subscription')) { + customOperations.push('subscriptions'); + } return customOperations.join(','); }; From 72ad88b3d5aa445d6f6e3830820e6816bc0a7908 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Fri, 18 Oct 2024 11:48:20 -0600 Subject: [PATCH 5/5] feat: sort order of auth modes --- .../src/__tests__/__functional__/metadata.test.ts | 10 ++++++---- .../src/internal/metadata.ts | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts index b8ba77e09e..2a947171b6 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/metadata.test.ts @@ -332,6 +332,12 @@ describe('metrics metadata', () => { `), authorizationModes: { defaultAuthorizationMode: 'API_KEY', + // assert output authorizationModes is sorted correctly + // aws_lambda should be last in list + lambdaConfig: { + function: new NodejsFunction(stack, 'TestAuthorizer', { entry: path.join(__dirname, 'authorizer.ts') }), + ttl: Duration.days(7), + }, apiKeyConfig: { expires: Duration.days(7) }, iamConfig: { identityPoolId: 'abc', @@ -342,10 +348,6 @@ describe('metrics metadata', () => { assumedBy: new ArnPrincipal('aws:iam::1234:root'), }), }, - lambdaConfig: { - function: new NodejsFunction(stack, 'TestAuthorizer', { entry: path.join(__dirname, 'authorizer.ts') }), - ttl: Duration.days(7), - }, }, }); const template = Template.fromStack(stack); diff --git a/packages/amplify-graphql-api-construct/src/internal/metadata.ts b/packages/amplify-graphql-api-construct/src/internal/metadata.ts index f11a498bc2..7b3b72dfab 100644 --- a/packages/amplify-graphql-api-construct/src/internal/metadata.ts +++ b/packages/amplify-graphql-api-construct/src/internal/metadata.ts @@ -19,7 +19,8 @@ export const getMetadataAuthorizationModes = (authorizationModes: AuthorizationM const authModes = Object.keys(authorizationModes) .map((mode) => configKeyToAuthMode[mode]) // remove values not found in mapping - .filter((mode) => !!mode); + .filter((mode) => !!mode) + .sort(); return authModes.join(','); };