Skip to content

Commit

Permalink
feat(cli): add sandbox mode warning to amplify status (#8078)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielleadams authored Sep 9, 2021
1 parent 96c081b commit e754661
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Object {
},
Object {
"apiKeyConfig": Object {
"apiKeyExpirationDate": undefined,
"apiKeyExpirationDays": undefined,
"description": undefined,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Object {
exports[`AppSyncAuthType to authConfig maps API_KEY correctly 1`] = `
Object {
"apiKeyConfig": Object {
"apiKeyExpirationDate": undefined,
"apiKeyExpirationDays": 120,
"description": undefined,
},
Expand Down Expand Up @@ -47,6 +48,7 @@ Object {

exports[`authConfig to AppSyncAuthType maps API_KEY auth correctly 1`] = `
Object {
"apiKeyExpirationDate": undefined,
"expirationTime": 120,
"keyDescription": "api key description",
"mode": "API_KEY",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
$TSContext,
open,
} from 'amplify-cli-core';
import { Duration, Expiration } from '@aws-cdk/core';
import { defineGlobalSandboxMode } from '../utils/global-sandbox-mode';

const serviceName = 'AppSync';
Expand Down Expand Up @@ -625,6 +626,8 @@ async function askApiKeyQuestions() {
];

const apiKeyConfig = await inquirer.prompt(apiKeyQuestions);
const apiKeyExpirationDaysNum = Number(apiKeyConfig.apiKeyExpirationDays);
apiKeyConfig.apiKeyExpirationDate = Expiration.after(Duration.days(apiKeyExpirationDaysNum)).date;

return {
authenticationType: 'API_KEY',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const authConfigToAppSyncAuthTypeMap: Record<string, (authConfig: any) => AppSyn
API_KEY: authConfig => ({
mode: 'API_KEY',
expirationTime: authConfig.apiKeyConfig.apiKeyExpirationDays,
apiKeyExpirationDate: authConfig.apiKeyConfig?.apiKeyExpirationDate,
keyDescription: authConfig.apiKeyConfig.description,
}),
AWS_IAM: () => ({
Expand All @@ -54,6 +55,7 @@ const appSyncAuthTypeToAuthConfigMap: Record<string, (authType: AppSyncAuthType)
authenticationType: 'API_KEY',
apiKeyConfig: {
apiKeyExpirationDays: authType.expirationTime,
apiKeyExpirationDate: authType?.apiKeyExpirationDate,
description: authType.keyDescription,
},
}),
Expand Down
5 changes: 2 additions & 3 deletions packages/amplify-cli-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ interface AmplifyToolkit {
) => $TSAny;
sharedQuestions: () => $TSAny;
showAllHelp: () => $TSAny;
showGlobalSandboxModeWarning: (context: $TSContext) => $TSAny;
showHelp: (header: string, commands: { name: string; description: string }[]) => $TSAny;
showHelpfulProviderLinks: (context: $TSContext) => $TSAny;
showResourceTable: () => $TSAny;
Expand Down Expand Up @@ -310,9 +311,7 @@ interface AmplifyToolkit {
leaveBreadcrumbs: (category: string, resourceName: string, breadcrumbs: unknown) => void;
readBreadcrumbs: (category: string, resourceName: string) => $TSAny;
loadRuntimePlugin: (context: $TSContext, pluginId: string) => Promise<$TSAny>;
getImportedAuthProperties: (
context: $TSContext,
) => {
getImportedAuthProperties: (context: $TSContext) => {
imported: boolean;
userPoolId?: string;
authRoleArn?: string;
Expand Down
15 changes: 4 additions & 11 deletions packages/amplify-cli/src/__tests__/commands/status.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { UnknownArgumentError } from 'amplify-cli-core';

describe('amplify status: ', () => {
const { run } = require('../../commands/status');
const runStatusCmd = run;
Expand All @@ -11,17 +9,10 @@ describe('amplify status: ', () => {
});

it('status run method should call context.amplify.showStatusTable', async () => {
const cliInput = {
command: 'status',
subCommands: [],
options: {
verbose: true,
},
};

const mockContextNoCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showGlobalSandboxModeWarning: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: mockPath }),
},
Expand All @@ -43,6 +34,7 @@ describe('amplify status: ', () => {
const mockContextWithVerboseOptionAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showGlobalSandboxModeWarning: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
Expand All @@ -61,6 +53,7 @@ describe('amplify status: ', () => {
const mockContextWithVerboseOptionWithCategoriesAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showGlobalSandboxModeWarning: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
Expand All @@ -82,6 +75,7 @@ describe('amplify status: ', () => {
const mockContextWithHelpSubcommandAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showGlobalSandboxModeWarning: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
Expand All @@ -94,5 +88,4 @@ describe('amplify status: ', () => {
//TBD: to move ViewResourceTableParams into a separate file for mocking instance functions.
expect(mockContextWithHelpSubcommandAndCLArgs.amplify.showStatusTable.mock.calls.length).toBe(0);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import fs from 'fs';
import { getAppSyncApiConfig, getApiKeyConfig, apiKeyIsActive, hasApiKey } from '../../../extensions/amplify-helpers/get-api-key-config';
import { stateManager } from 'amplify-cli-core';

let amplifyMeta;

jest.mock('amplify-cli-core', () => {
const original = jest.requireActual('amplify-cli-core');
return {
...original,
stateManager: {
metaFileExists: jest.fn(),
getMeta: jest.fn().mockImplementation(() => JSON.parse(amplifyMeta.toString())),
},
};
});

const stateManager_mock = stateManager as jest.Mocked<typeof stateManager>;

describe('getAppSyncApiConfig', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('returns the api object', async () => {
const result = getAppSyncApiConfig();

expect(result).toStrictEqual({
service: 'AppSync',
providerPlugin: 'awscloudformation',
output: {
authConfig: {
defaultAuthentication: {
authenticationType: 'AWS_IAM',
},
additionalAuthenticationProviders: [
{
authenticationType: 'API_KEY',
apiKeyConfig: {
apiKeyExpirationDays: 2,
apiKeyExpirationDate: '2021-08-20T20:38:07.585Z',
description: '',
},
},
],
},
globalSandboxModeConfig: {
dev: {
enabled: true,
},
},
},
});
});
});

describe('getApiKeyConfig', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('returns the api key config', () => {
const result = getApiKeyConfig();

expect(result).toStrictEqual({
apiKeyExpirationDays: 2,
apiKeyExpirationDate: '2021-08-20T20:38:07.585Z',
description: '',
});
});
});

describe('apiKeyIsActive', () => {
describe('with expired key', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('returns false', () => {
expect(apiKeyIsActive()).toBe(false);
});
});

describe('with no api key config', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta-3.json`);
});

it('returns false', () => {
expect(apiKeyIsActive()).toBe(false);
});
});
});

describe('hasApiKey', () => {
describe('if api key config is present', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('returns true if api key is present', () => {
expect(hasApiKey()).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
globalSandboxModeEnabled,
showGlobalSandboxModeWarning,
} from '../../../extensions/amplify-helpers/show-global-sandbox-mode-warning';
import { $TSContext } from '../../../../../amplify-cli-core/lib';
import fs from 'fs';
import chalk from 'chalk';

let ctx, amplifyMeta;

jest.mock('amplify-cli-core', () => ({
stateManager: {
getMeta: jest.fn(() => JSON.parse(amplifyMeta.toString())),
},
}));

describe('global sandbox mode warning', () => {
beforeEach(() => {
const envName = 'dev';
ctx = {
amplify: {
getEnvInfo() {
return { envName };
},
},
print: {
info() {
// noop
},
},
} as unknown as $TSContext;
});

describe('globalSandboxModeEnabled', () => {
describe('enabled', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('returns true', async () => {
expect(globalSandboxModeEnabled(ctx)).toBe(true);
});
});

describe('not specified', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta-2.json`);
});

it('returns false', async () => {
expect(globalSandboxModeEnabled(ctx)).toBe(false);
});
});
});

describe('showGlobalSandboxModeWarning', () => {
describe('sandbox mode enabled', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta.json`);
});

it('prints warning message', async () => {
jest.spyOn(ctx.print, 'info');

await showGlobalSandboxModeWarning(ctx);

expect(ctx.print.info).toBeCalledWith(`
⚠️ WARNING: ${chalk.green('"type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key"')} in your GraphQL schema
allows public create, read, update, and delete access to all models via API Key. This
should only be used for testing purposes. API Key expiration date is: 8/20/2021
To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth
`);
});
});

describe('sandbox mode not specified', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta-2.json`);
});

it('does not print warning', async () => {
jest.spyOn(ctx.print, 'info');

await showGlobalSandboxModeWarning(ctx);

expect(ctx.print.info).toBeCalledTimes(0);
});
});

describe('no api key config', () => {
beforeAll(() => {
amplifyMeta = fs.readFileSync(`${__dirname}/testData/mockLocalCloud/amplify-meta-3.json`);
});

it('does not print warning', async () => {
jest.spyOn(ctx.print, 'info');

await showGlobalSandboxModeWarning(ctx);

expect(ctx.print.info).toBeCalledTimes(0);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"api": {
"ampapp": {
"service": "AppSync",
"providerPlugin": "awscloudformation",
"output": {
"authConfig": {
"defaultAuthentication": {
"authenticationType": "AWS_IAM"
},
"additionalAuthenticationProviders": [
{
"authenticationType": "API_KEY",
"apiKeyConfig": {
"apiKeyExpirationDays": 2,
"apiKeyExpirationDate": "2021-08-20T20:38:07.585Z",
"description": ""
}
}
]
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"api": {
"ampapp": {
"service": "AppSync",
"providerPlugin": "awscloudformation",
"output": {
"authConfig": {
"defaultAuthentication": {
"authenticationType": "AWS_IAM"
},
"additionalAuthenticationProviders": []
}
}
}
}
}
Loading

0 comments on commit e754661

Please sign in to comment.