Skip to content

Commit

Permalink
feat(amplify-provider-awscloudformation): change sandbox mode syntax …
Browse files Browse the repository at this point in the history
…in schema (#8592)
  • Loading branch information
danielleadams committed Oct 31, 2021
1 parent e3a2afb commit a3bdd44
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 95 deletions.
@@ -1,21 +1,10 @@
import { defineGlobalSandboxMode } from '../../../../provider-utils/awscloudformation/utils/global-sandbox-mode';
import { $TSContext } from 'amplify-cli-core';

describe('global sandbox mode GraphQL directive', () => {
it('returns AMPLIFY_DIRECTIVE type with code comment, directive, and env name', () => {
const envName = 'envone';
const ctx = <$TSContext>{
amplify: {
getEnvInfo() {
return { envName };
},
},
};

expect(defineGlobalSandboxMode(ctx))
.toBe(`# This allows public create, read, update, and delete access for a limited time to all models via API Key.
# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n
it('returns input AMPLIFY with code comment', () => {
expect(defineGlobalSandboxMode()).toEqual(`# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql-transformer/auth
input AMPLIFY { global_auth_rule: AuthorizationRule = { allow: public } } # FOR TESTING ONLY!\n
`);
});
});
Expand Up @@ -419,7 +419,7 @@ export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilen

const { templateSelection } = await inquirer.prompt(templateSelectionQuestion);
const schemaFilePath = path.join(graphqlSchemaDir, templateSelection);
schemaContent += transformerVersion === 2 && templateSelection !== blankSchemaFile ? defineGlobalSandboxMode(context) : '';
schemaContent += transformerVersion === 2 ? defineGlobalSandboxMode() : '';
schemaContent += fs.readFileSync(schemaFilePath, 'utf8');

return {
Expand Down Expand Up @@ -664,8 +664,8 @@ async function addLambdaAuthorizerChoice(context) {
const transformerVersion = providerPlugin.getTransformerVersion(context);
if (transformerVersion === 2 && !authProviderChoices.some(choice => choice.value == 'AWS_LAMBDA')) {
authProviderChoices.push({
name: 'Lambda',
value: 'AWS_LAMBDA',
name: 'Lambda',
value: 'AWS_LAMBDA',
});
}
}
Expand Down Expand Up @@ -1073,7 +1073,7 @@ async function askLambdaQuestion(context) {
const lambdaAuthorizerConfig = {
lambdaFunction,
ttlSeconds,
}
};

return {
authenticationType: 'AWS_LAMBDA',
Expand Down Expand Up @@ -1130,9 +1130,7 @@ async function askLambdaFromProject(context: $TSContext) {
default: lambdaFunctions[0],
});

await context.amplify.invokePluginMethod(context, 'function', undefined, 'addAppSyncInvokeMethodPermission', [
answer.lambdaFunction,
]);
await context.amplify.invokePluginMethod(context, 'function', undefined, 'addAppSyncInvokeMethodPermission', [answer.lambdaFunction]);

return { lambdaFunction: answer.lambdaFunction };
}
Expand Down Expand Up @@ -1180,4 +1178,4 @@ async function createLambdaAuthorizerFunction(context: $TSContext) {
context.print.success(`Successfully added ${functionName} function locally`);

return functionName;
};
}
@@ -1,10 +1,6 @@
import { $TSContext } from 'amplify-cli-core';

export function defineGlobalSandboxMode(context: $TSContext): string {
const envName = context.amplify.getEnvInfo().envName;

return `# This allows public create, read, update, and delete access for a limited time to all models via API Key.
# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n
export function defineGlobalSandboxMode(): string {
return `# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql-transformer/auth
input AMPLIFY { global_auth_rule: AuthorizationRule = { allow: public } } # FOR TESTING ONLY!\n
`;
}
53 changes: 53 additions & 0 deletions packages/amplify-e2e-core/src/categories/api.ts
Expand Up @@ -65,6 +65,59 @@ export function addApiWithoutSchema(cwd: string, opts: Partial<AddApiOptions & {
});
}

export function addApiWithOneModel(cwd: string, opts: Partial<AddApiOptions & { apiKeyExpirationDays: number }> = {}) {
const options = _.assign(defaultOptions, opts);
return new Promise<void>((resolve, reject) => {
spawn(getCLIPath(options.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true })
.wait('Please select from one of the below mentioned services:')
.sendCarriageReturn()
.wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/)
.sendCarriageReturn()
.wait('Choose a schema template:')
.sendCarriageReturn()
.wait('Do you want to edit the schema now?')
.sendConfirmNo()
.wait(
'"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud',
)
.sendEof()
.run((err: Error) => {
if (!err) {
resolve();
} else {
reject(err);
}
});
});
}

export function addApiWithThreeModels(cwd: string, opts: Partial<AddApiOptions & { apiKeyExpirationDays: number }> = {}) {
const options = _.assign(defaultOptions, opts);
return new Promise<void>((resolve, reject) => {
spawn(getCLIPath(options.testingWithLatestCodebase), ['add', 'api'], { cwd, stripColors: true })
.wait('Please select from one of the below mentioned services:')
.sendCarriageReturn()
.wait(/.*Here is the GraphQL API that we will create. Select a setting to edit or continue.*/)
.sendCarriageReturn()
.wait('Choose a schema template:')
.sendKeyDown(1)
.sendCarriageReturn()
.wait('Do you want to edit the schema now?')
.sendConfirmNo()
.wait(
'"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud',
)
.sendEof()
.run((err: Error) => {
if (!err) {
resolve();
} else {
reject(err);
}
});
});
}

export function addApiWithBlankSchema(cwd: string, opts: Partial<AddApiOptions & { apiKeyExpirationDays: number }> = {}) {
const options = _.assign(defaultOptions, opts);
return new Promise<void>((resolve, reject) => {
Expand Down
@@ -1,4 +1,6 @@
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev")
input AMPLIFY {
global_auth_rule: AuthorizationRule = { allow: public }
}

type Todo @model {
id: ID!
Expand Down
47 changes: 47 additions & 0 deletions packages/amplify-e2e-tests/src/__tests__/global_sandbox.test.ts
@@ -0,0 +1,47 @@
import {
initJSProjectWithProfile,
deleteProject,
addFeatureFlag,
addApiWithoutSchema,
addApiWithOneModel,
addApiWithThreeModels,
updateApiSchema,
apiGqlCompile,
amplifyPush,
} from 'amplify-e2e-core';
import { createNewProjectDir, deleteProjectDir } from 'amplify-e2e-core';

describe('global sandbox mode', () => {
let projectDir: string;
let apiName = 'sandbox';

beforeEach(async () => {
projectDir = await createNewProjectDir('sandbox');
await initJSProjectWithProfile(projectDir);
addFeatureFlag(projectDir, 'graphqltransformer', 'useexperimentalpipelinedtransformer', true);
});

afterEach(async () => {
await deleteProject(projectDir);
deleteProjectDir(projectDir);
});

it('compiles schema with one model and pushes to cloud', async () => {
await addApiWithOneModel(projectDir);
await apiGqlCompile(projectDir, true);
await amplifyPush(projectDir, true);
});

it.skip('compiles schema with three models and pushes to cloud', async () => {
await addApiWithThreeModels(projectDir);
await apiGqlCompile(projectDir, true);
await amplifyPush(projectDir, true);
});

it('compiles schema user-added schema and pushes to cloud', async () => {
await addApiWithoutSchema(projectDir, { apiName });
updateApiSchema(projectDir, apiName, 'model_with_sandbox_mode.graphql');
await apiGqlCompile(projectDir, true);
await amplifyPush(projectDir, true);
});
});
Expand Up @@ -103,6 +103,7 @@ scalar AWSPhone
scalar AWSIPAddress
scalar BigInt
scalar Double
scalar AuthorizationRule
`);

export const EXTRA_DIRECTIVES_DOCUMENT = parse(`
Expand Down
Expand Up @@ -27,6 +27,8 @@ import { DEFAULT_SCHEMA_DEFINITION } from '../utils/defaultSchema';
import { TransformerContextOutputProvider } from '@aws-amplify/graphql-transformer-interfaces';
import assert from 'assert';

const AMPLIFY = 'AMPLIFY';

export function blankObject(name: string): ObjectTypeDefinitionNode {
return {
kind: 'ObjectTypeDefinition',
Expand Down Expand Up @@ -73,6 +75,7 @@ export class TransformerOutput implements TransformerContextOutputProvider {
case Kind.ENUM_TYPE_DEFINITION:
case Kind.UNION_TYPE_DEFINITION:
const typeDef = inputDef as TypeDefinitionNode;
if (this.isAmplifyInput(typeDef.name.value)) break;
if (!this.getType(typeDef.name.value)) {
this.addType(typeDef);
}
Expand Down Expand Up @@ -612,4 +615,8 @@ export class TransformerOutput implements TransformerContextOutputProvider {
directives: [],
};
}

private isAmplifyInput(inputName: string): boolean {
return inputName === AMPLIFY;
}
}
@@ -1,4 +1,4 @@
import { showSandboxModePrompts, removeSandboxDirectiveFromSchema, showGlobalSandboxModeWarning } from '../../utils/sandbox-mode-helpers';
import { showSandboxModePrompts, showGlobalSandboxModeWarning, schemaHasSandboxModeEnabled } from '../../utils/sandbox-mode-helpers';
import { $TSContext } from 'amplify-cli-core';
import chalk from 'chalk';
import * as prompts from 'amplify-prompts';
Expand Down Expand Up @@ -35,9 +35,9 @@ describe('sandbox mode helpers', () => {
expect(prompts.printer.info).toBeCalledWith(
`
⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If
you'd like to disable, remove ${chalk.green('"type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key"')}
you'd like to disable, remove ${chalk.green('"input AMPLIFY { global_auth_rule: AuthorizationRule = { allow: public } }"')}
from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with
sandbox mode disabled in '${ctx.amplify.getEnvInfo().envName}', do not create an API Key.
sandbox mode disabled, do not create an API Key.
`,
'yellow',
);
Expand All @@ -59,59 +59,72 @@ sandbox mode disabled in '${ctx.amplify.getEnvInfo().envName}', do not create an
});
});

describe('removeSandboxDirectiveFromSchema', () => {
it('removes sandbox mode directive', () => {
describe('schemaHasSandboxModeEnabled', () => {
it('parses sandbox AMPLIFY input on schema', () => {
const schema = `
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev")
input AMPLIFY { global_auth_rule: AuthorizationRule = { allow: public } }
`;

expect(removeSandboxDirectiveFromSchema(schema)).toEqual(`
`);
expect(schemaHasSandboxModeEnabled(schema)).toEqual(true);
});

it('does not change user schema with directive', () => {
it('passes through when AMPLIFY input is not present', () => {
const schema = `
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev10105") # FOR TESTING ONLY!
type Todo @model {
id: ID!
name: String!
description: String
}
type Todo @model {
id: ID!
content: String
}
`;

expect(removeSandboxDirectiveFromSchema(schema)).toEqual(`
# FOR TESTING ONLY!
type Todo @model {
id: ID!
name: String!
description: String
}
`);
expect(schemaHasSandboxModeEnabled(schema)).toEqual(false);
});

it('does not change user schema with directive and single quotes', () => {
const schema = `
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: 'dev10105') # FOR TESTING ONLY!
describe('input AMPLIFY has incorrect values', () => {
it('checks for "global_auth_rule"', () => {
const schema = `
input AMPLIFY { auth_rule: AuthenticationRule = { allow: public } }
`;

type Todo @model {
id: ID!
name: String!
description: String
}
`;
expect(() => schemaHasSandboxModeEnabled(schema)).toThrow(
Error('input AMPLIFY requires "global_auth_rule" field. Learn more here: https://docs.amplify.aws/cli/graphql-transformer/auth'),
);
});

expect(removeSandboxDirectiveFromSchema(schema)).toEqual(`
# FOR TESTING ONLY!
it('guards for AuthorizationRule', () => {
const schema = `
input AMPLIFY { global_auth_rule: AuthenticationRule = { allow: public } }
`;

type Todo @model {
id: ID!
name: String!
description: String
}
`);
expect(() => schemaHasSandboxModeEnabled(schema)).toThrow(
Error(
'There was a problem with your auth configuration. Learn more about auth here: https://docs.amplify.aws/cli/graphql-transformer/auth',
),
);
});

it('checks for "allow" field name', () => {
const schema = `
input AMPLIFY { global_auth_rule: AuthorizationRule = { allows: public } }
`;

expect(() => schemaHasSandboxModeEnabled(schema)).toThrow(
Error(
'There was a problem with your auth configuration. Learn more about auth here: https://docs.amplify.aws/cli/graphql-transformer/auth',
),
);
});

it('checks for "public" value from "allow" field', () => {
const schema = `
input AMPLIFY { global_auth_rule: AuthorizationRule = { allow: private } }
`;

expect(() => schemaHasSandboxModeEnabled(schema)).toThrowError(
Error(
'There was a problem with your auth configuration. Learn more about auth here: https://docs.amplify.aws/cli/graphql-transformer/auth',
),
);
});
});
});
});

0 comments on commit a3bdd44

Please sign in to comment.