Skip to content

Commit

Permalink
Merge branch 'master' into benisrae/use-lerna-verison
Browse files Browse the repository at this point in the history
  • Loading branch information
Elad Ben-Israel authored Feb 25, 2020
2 parents 3860fba + 2bce689 commit 6857409
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 34 deletions.
1 change: 0 additions & 1 deletion .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ pull_request_rules:
strict_method: merge
delete_head_branch: {}
conditions:
- base!=release
- -title~=(WIP|wip)
- -label~=(blocked|do-not-merge)
# Only if no-squash is set
Expand Down
13 changes: 10 additions & 3 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,16 @@ export class ApiStack extends Stack {
logConfig: {
fieldLogLevel: FieldLogLevel.ALL,
},
userPoolConfig: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW,
authorizationConfig: {
defaultAuthorization: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW,
},
additionalAuthorizationModes: [
{
apiKeyDesc: 'My API Key',
},
],
},
schemaDefinitionFile: './schema.graphql',
});
Expand Down
131 changes: 110 additions & 21 deletions packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { IUserPool } from "@aws-cdk/aws-cognito";
import { Table } from '@aws-cdk/aws-dynamodb';
import { IGrantable, IPrincipal, IRole, ManagedPolicy, Role, ServicePrincipal } from "@aws-cdk/aws-iam";
import { IFunction } from "@aws-cdk/aws-lambda";
import { Construct, IResolvable } from "@aws-cdk/core";
import { Construct, Duration, IResolvable } from "@aws-cdk/core";
import { readFileSync } from "fs";
import { CfnDataSource, CfnGraphQLApi, CfnGraphQLSchema, CfnResolver } from "./appsync.generated";
import { CfnApiKey, CfnDataSource, CfnGraphQLApi, CfnGraphQLSchema, CfnResolver } from "./appsync.generated";

/**
* Marker interface for the different authorization modes.
*/
export interface AuthMode { }

/**
* enum with all possible values for Cognito user-pool default actions
Expand All @@ -23,7 +28,7 @@ export enum UserPoolDefaultAction {
/**
* Configuration for Cognito user-pools in AppSync
*/
export interface UserPoolConfig {
export interface UserPoolConfig extends AuthMode {

/**
* The Cognito user pool to use as identity source
Expand All @@ -43,6 +48,51 @@ export interface UserPoolConfig {
readonly defaultAction?: UserPoolDefaultAction;
}

function isUserPoolConfig(obj: unknown): obj is UserPoolConfig {
return (obj as UserPoolConfig).userPool !== undefined;
}

/**
* Configuration for API Key authorization in AppSync
*/
export interface ApiKeyConfig extends AuthMode {
/**
* Unique description of the API key
*/
readonly apiKeyDesc: string;

/**
* The time from creation time after which the API key expires, using RFC3339 representation.
* It must be a minimum of 1 day and a maximum of 365 days from date of creation.
* Rounded down to the nearest hour.
* @default - 7 days from creation time
*/
readonly expires?: string;
}

function isApiKeyConfig(obj: unknown): obj is ApiKeyConfig {
return (obj as ApiKeyConfig).apiKeyDesc !== undefined;
}

/**
* Configuration of the API authorization modes.
*/
export interface AuthorizationConfig {
/**
* Optional authorization configuration
*
* @default - API Key authorization
*/
readonly defaultAuthorization?: AuthMode;

/**
* Additional authorization modes
*
* @default - No other modes
*/
readonly additionalAuthorizationModes?: [AuthMode]
}

/**
* log-level for fields in AppSync
*/
Expand Down Expand Up @@ -90,11 +140,11 @@ export interface GraphQLApiProps {
readonly name: string;

/**
* Optional user pool authorizer configuration
* Optional authorization configuration
*
* @default - Do not use Cognito auth
* @default - API Key authorization
*/
readonly userPoolConfig?: UserPoolConfig;
readonly authorizationConfig?: AuthorizationConfig;

/**
* Logging configuration for this api
Expand Down Expand Up @@ -145,7 +195,6 @@ export class GraphQLApi extends Construct {
public readonly schema: CfnGraphQLSchema;

private api: CfnGraphQLApi;
private authenticationType: string;

constructor(scope: Construct, id: string, props: GraphQLApiProps) {
super(scope, id);
Expand All @@ -156,22 +205,9 @@ export class GraphQLApi extends Construct {
apiLogsRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'));
}

if (props.userPoolConfig) {
this.authenticationType = 'AMAZON_COGNITO_USER_POOLS';
} else {
this.authenticationType = 'API_KEY';
}

this.api = new CfnGraphQLApi(this, 'Resource', {
name: props.name,
authenticationType: this.authenticationType,
...props.userPoolConfig && {
userPoolConfig: {
userPoolId: props.userPoolConfig.userPool.userPoolId,
awsRegion: props.userPoolConfig.userPool.stack.region,
defaultAction: props.userPoolConfig.defaultAction ? props.userPoolConfig.defaultAction.toString() : 'ALLOW',
},
},
authenticationType: 'API_KEY',
...props.logConfig && {
logConfig: {
cloudWatchLogsRoleArn: apiLogsRole ? apiLogsRole.roleArn : undefined,
Expand All @@ -186,6 +222,10 @@ export class GraphQLApi extends Construct {
this.graphQlUrl = this.api.attrGraphQlUrl;
this.name = this.api.name;

if (props.authorizationConfig) {
this.setupAuth(props.authorizationConfig);
}

let definition;
if (props.schemaDefinition) {
definition = props.schemaDefinition;
Expand Down Expand Up @@ -230,6 +270,55 @@ export class GraphQLApi extends Construct {
});
}

private setupAuth(auth: AuthorizationConfig) {
if (isUserPoolConfig(auth.defaultAuthorization)) {
const { authenticationType, userPoolConfig } = this.userPoolDescFrom(auth.defaultAuthorization);
this.api.authenticationType = authenticationType;
this.api.userPoolConfig = userPoolConfig;
} else if (isApiKeyConfig(auth.defaultAuthorization)) {
this.api.authenticationType = this.apiKeyDesc(auth.defaultAuthorization).authenticationType;
}

this.api.additionalAuthenticationProviders = [];
for (const mode of (auth.additionalAuthorizationModes || [])) {
if (isUserPoolConfig(mode)) {
this.api.additionalAuthenticationProviders.push(this.userPoolDescFrom(mode));
} else if (isApiKeyConfig(mode)) {
this.api.additionalAuthenticationProviders.push(this.apiKeyDesc(mode));
}
}
}

private userPoolDescFrom(upConfig: UserPoolConfig): { authenticationType: string; userPoolConfig: CfnGraphQLApi.UserPoolConfigProperty } {
return {
authenticationType: 'AMAZON_COGNITO_USER_POOLS',
userPoolConfig: {
appIdClientRegex: upConfig.appIdClientRegex,
userPoolId: upConfig.userPool.userPoolId,
awsRegion: upConfig.userPool.stack.region,
defaultAction: upConfig.defaultAction ? upConfig.defaultAction.toString() : 'ALLOW',
}
};
}

private apiKeyDesc(akConfig: ApiKeyConfig): { authenticationType: string } {
let expires: number | undefined;
if (akConfig.expires) {
expires = new Date(akConfig.expires).valueOf();
const now = Date.now();
const days = (d: number) => now + Duration.days(d).toMilliseconds();
if (expires < days(1) || expires > days(365)) {
throw Error("API key expiration must be between 1 and 365 days.");
}
expires = Math.round(expires / 1000);
}
new CfnApiKey(this, `${akConfig.apiKeyDesc || ''}ApiKey`, {
expires,
description: akConfig.apiKeyDesc,
apiId: this.apiId,
});
return { authenticationType: 'API_KEY' };
}
}

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/@aws-cdk/aws-appsync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,12 @@
"engines": {
"node": ">= 10.3.0"
},
"awslint": {
"exclude": [
"no-unused-type:@aws-cdk/aws-appsync.ApiKeyConfig",
"no-unused-type:@aws-cdk/aws-appsync.UserPoolConfig",
"no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction"
]
},
"stability": "experimental"
}
}
95 changes: 93 additions & 2 deletions packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,101 @@
{
"Resources": {
"PoolsmsRoleC3352CE6": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "awsappsyncintegPool5D14B05B"
}
},
"Effect": "Allow",
"Principal": {
"Service": "cognito-idp.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": "sns:Publish",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "sns-publish"
}
]
}
},
"PoolD3F588B8": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
"EmailVerificationMessage": "Hello {username}, Your verification code is {####}",
"EmailVerificationSubject": "Verify your new account",
"LambdaConfig": {},
"SmsConfiguration": {
"ExternalId": "awsappsyncintegPool5D14B05B",
"SnsCallerArn": {
"Fn::GetAtt": [
"PoolsmsRoleC3352CE6",
"Arn"
]
}
},
"SmsVerificationMessage": "The verification code to your new account is {####}",
"UserPoolName": "myPool",
"VerificationMessageTemplate": {
"DefaultEmailOption": "CONFIRM_WITH_CODE",
"EmailMessage": "Hello {username}, Your verification code is {####}",
"EmailSubject": "Verify your new account",
"SmsMessage": "The verification code to your new account is {####}"
}
}
},
"ApiF70053CD": {
"Type": "AWS::AppSync::GraphQLApi",
"Properties": {
"AuthenticationType": "API_KEY",
"Name": "demoapi"
"AuthenticationType": "AMAZON_COGNITO_USER_POOLS",
"Name": "demoapi",
"AdditionalAuthenticationProviders": [
{
"AuthenticationType": "API_KEY"
}
],
"UserPoolConfig": {
"AwsRegion": {
"Ref": "AWS::Region"
},
"DefaultAction": "ALLOW",
"UserPoolId": {
"Ref": "PoolD3F588B8"
}
}
}
},
"ApiMyAPIKeyApiKeyACDEE2CC": {
"Type": "AWS::AppSync::ApiKey",
"Properties": {
"ApiId": {
"Fn::GetAtt": [
"ApiF70053CD",
"ApiId"
]
},
"Description": "My API Key"
}
},
"ApiSchema510EECD7": {
Expand Down
20 changes: 19 additions & 1 deletion packages/@aws-cdk/aws-appsync/test/integ.graphql.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import { UserPool } from '@aws-cdk/aws-cognito';
import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb';
import { App, Stack } from '@aws-cdk/core';
import { join } from 'path';
import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, Values } from '../lib';
import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, UserPoolDefaultAction, Values } from '../lib';

const app = new App();
const stack = new Stack(app, 'aws-appsync-integ');

const userPool = new UserPool(stack, 'Pool', {
userPoolName: 'myPool',
});

const api = new GraphQLApi(stack, 'Api', {
name: `demoapi`,
schemaDefinitionFile: join(__dirname, 'schema.graphql'),
authorizationConfig: {
defaultAuthorization: {
userPool,
defaultAction: UserPoolDefaultAction.ALLOW,
},
additionalAuthorizationModes: [
{
apiKeyDesc: 'My API Key',
// Can't specify a date because it will inevitably be in the past.
// expires: '2019-02-05T12:00:00Z',
},
],
},
});

const customerTable = new Table(stack, 'CustomerTable', {
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@types/semver": "^7.1.0",
"@types/sinon": "^7.5.0",
"@types/table": "^4.0.7",
"@types/uuid": "^3.4.7",
"@types/uuid": "^7.0.0",
"@types/yaml": "^1.2.0",
"@types/yargs": "^15.0.3",
"aws-sdk-mock": "^5.0.0",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2134,10 +2134,10 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5"
integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==

"@types/uuid@^3.4.7":
version "3.4.7"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.7.tgz#51d42247473bc00e38cc8dfaf70d936842a36c03"
integrity sha512-C2j2FWgQkF1ru12SjZJyMaTPxs/f6n90+5G5qNakBxKXjTBc/YTSelHh4Pz1HUDwxFXD9WvpQhOGCDC+/Y4mIQ==
"@types/uuid@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.0.tgz#9f6993ccc8210efa90bda7e1afabbb06a9f860cd"
integrity sha512-RiX1I0lK9WFLFqy2xOxke396f0wKIzk5sAll0tL4J4XDYJXURI7JOs96XQb3nP+2gEpQ/LutBb66jgiT5oQshQ==

"@types/yaml@1.2.0", "@types/yaml@^1.2.0":
version "1.2.0"
Expand Down

0 comments on commit 6857409

Please sign in to comment.