Skip to content

Commit

Permalink
feat(cli): add sandbox mode warning to amplify status
Browse files Browse the repository at this point in the history
  • Loading branch information
danielleadams committed Sep 8, 2021
1 parent acaff15 commit f2860fd
Show file tree
Hide file tree
Showing 19 changed files with 420 additions and 43 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';

const serviceName = 'AppSync';
const elasticContainerServiceName = 'ElasticContainer';
Expand Down Expand Up @@ -508,9 +509,11 @@ export async function askAdditionalAuthQuestions(context, authConfig, defaultAut
if (await context.prompt.confirm('Configure additional auth types?')) {
// Get additional auth configured
const remainingAuthProviderChoices = authProviderChoices.filter(p => p.value !== defaultAuthType);
const currentAdditionalAuth = ((currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders
? currentAuthConfig.additionalAuthenticationProviders
: []) as any[]).map(authProvider => authProvider.authenticationType);
const currentAdditionalAuth = (
(currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders
? currentAuthConfig.additionalAuthenticationProviders
: []) as any[]
).map(authProvider => authProvider.authenticationType);

const additionalProvidersQuestion: CheckboxQuestion = {
type: 'checkbox',
Expand Down Expand Up @@ -619,6 +622,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 Expand Up @@ -677,9 +682,10 @@ function validateDays(input) {
}

function validateIssuerUrl(input) {
const isValid = /^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test(
input,
);
const isValid =
/^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test(
input,
);

if (!isValid) {
return 'The value must be a valid URI with a trailing forward slash. HTTPS must be used instead of HTTP unless you are using localhost.';
Expand Down Expand Up @@ -779,8 +785,8 @@ const buildPolicyResource = (resourceName: string, path: string | null) => {
{
Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
},
...(path ? [path] : [])
]
...(path ? [path] : []),
],
],
};
};
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);
});
});
});
});
Loading

0 comments on commit f2860fd

Please sign in to comment.