Skip to content

feat: support https for mock util plugin #13700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslint-dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
"junit",
"jwks",
"keyless",
"keyout",
"keyphrase",
"kinesis",
"lang",
Expand Down Expand Up @@ -278,6 +279,7 @@
"openid",
"openpgp",
"opensearch",
"openssl",
"orgs",
"Parti",
"parens",
Expand Down
6 changes: 6 additions & 0 deletions packages/amplify-appsync-simulator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export class AmplifyAppSyncSimulator {
// (undocumented)
init(config: AmplifyAppSyncSimulatorConfig): void;
// (undocumented)
get isHttps(): boolean;
// (undocumented)
get localhostUrl(): string;
// (undocumented)
get pubsub(): PubSub;
Expand Down Expand Up @@ -268,6 +270,10 @@ export type AppSyncSimulatorSchemaConfig = AppSyncMockFile;
export type AppSyncSimulatorServerConfig = {
port?: number;
wsPort?: number;
httpsConfig?: {
sslKeyPath: string;
sslCertPath: string;
};
};

// @public (undocumented)
Expand Down
3 changes: 3 additions & 0 deletions packages/amplify-appsync-simulator/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export class AmplifyAppSyncSimulator {
get localhostUrl(): string {
return this._server.localhostUrl.graphql;
}
get isHttps(): boolean {
return this._server.isHttps;
}
get config(): AmplifyAppSyncSimulatorConfig {
return this._config;
}
Expand Down
32 changes: 29 additions & 3 deletions packages/amplify-appsync-simulator/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { OperationServer } from './operations';
import { AmplifyAppSyncSimulator } from '..';
import { AppSyncSimulatorServerConfig } from '../type-definition';
import { Server, createServer } from 'http';
import { createServer as createHttpsServer } from 'https';
import { readFileSync } from 'fs';
import { fromEvent } from 'promise-toolbox';
import { address as getLocalIpAddress } from 'ip';
import { AppSyncSimulatorSubscriptionServer } from './websocket-subscription';
Expand All @@ -17,10 +19,30 @@ export class AppSyncSimulatorServer {
private _realTimeSubscriptionServer: AppSyncSimulatorSubscriptionServer;
private _url: string;
private _localhostUrl: string;
private _isHttps = false;

constructor(private config: AppSyncSimulatorServerConfig, private simulatorContext: AmplifyAppSyncSimulator) {
this._operationServer = new OperationServer(config, simulatorContext);
this._httpServer = createServer(this._operationServer.app);

// Check if the https configuration is not provided
if (!config.httpsConfig) {
this._httpServer = createServer(this._operationServer.app);
} else {
try {
// Read the ssl cert and key
const sslOptions = {
key: readFileSync(config.httpsConfig.sslKeyPath),
cert: readFileSync(config.httpsConfig.sslCertPath),
};
// Set the isHttps flag to true
this._isHttps = true;
// Create the https server
this._httpServer = createHttpsServer(sslOptions, this._operationServer.app);
} catch (e) {
throw new Error(`SSL key and certificate path provided are invalid. ${e.message}`);
}
}

this._realTimeSubscriptionServer = new AppSyncSimulatorSubscriptionServer(
simulatorContext,
this._httpServer,
Expand Down Expand Up @@ -49,8 +71,9 @@ export class AppSyncSimulatorServer {

this._httpServer.listen(port);
await fromEvent(this._httpServer, 'listening').then(() => {
this._url = `http://${getLocalIpAddress()}:${port}`;
this._localhostUrl = `http://localhost:${port}`;
const protocol = this._isHttps ? 'https' : 'http';
this._url = `${protocol}://${getLocalIpAddress()}:${port}`;
this._localhostUrl = `${protocol}://localhost:${port}`;
});
}

Expand All @@ -68,4 +91,7 @@ export class AppSyncSimulatorServer {
graphql: this._localhostUrl,
};
}
get isHttps() {
return this._isHttps;
}
}
4 changes: 4 additions & 0 deletions packages/amplify-appsync-simulator/src/type-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ export type AmplifyAppSyncSimulatorConfig = {
export type AppSyncSimulatorServerConfig = {
port?: number;
wsPort?: number;
httpsConfig?: {
sslKeyPath: string;
sslCertPath: string;
};
};

export type AmplifyAppSyncSimulatorRequestContext = {
Expand Down
2 changes: 1 addition & 1 deletion packages/amplify-graphiql-explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ This is the package that contains graphiql explorer assets for amplify-appsync-s

## Development Mode

When making changes to grapiql-explore, run `yarn start`. All the requests get proxied to `http://localhost:20002/`
When making changes to grapiql-explorer, run `yarn start`. All the requests get proxied to `http://localhost:20002/` by default (If you use the --https flag on `amplify mock`, change the proxy from `http://localhost:20002/` to `https://localhost:20002/` in package.json.)
29 changes: 29 additions & 0 deletions packages/amplify-util-mock/src/__tests__/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ describe('Test Mock API methods', () => {
await expect(mockContext.print.error).toHaveBeenCalledWith('Failed to start API Mocking.');
});

it('shows error message and resolution when https enabled if SSL key and certificate paths are not provided', async () => {
ConfigOverrideManager.getInstance = jest.fn().mockReturnValue(jest.fn);
const mockContext = {
print: {
red: jest.fn(),
green: jest.fn(),
error: jest.fn(),
},
parameters: {
options: {
help: false,
},
},
input: {
argv: ['--https'],
},
amplify: {
getEnvInfo: jest.fn().mockReturnValue({ projectPath: mockProjectRoot }),
pathManager: {
getGitIgnoreFilePath: jest.fn(),
},
},
} as unknown as $TSContext;
await run(mockContext);
await expect(mockContext.print.error).toHaveBeenCalledWith(
'\nThe --https option must be followed by the path to the SSL key and the path to the SSL certificate.\n',
);
});

it('attempts to set custom port correctly', async () => {
const GRAPHQL_PORT = 8081;
const mockContext = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getHttpsConfig } from '../utils/get-https-config';

describe('getHttpsConfig', () => {
let context;

beforeEach(() => {
context = {
input: {
argv: [],
},
print: {
error: jest.fn(),
},
};
});

it('returns paths when --https option is followed by key and cert paths', () => {
context.input.argv = ['--https', '/path/to/key', '/path/to/cert'];

const config = getHttpsConfig(context);

expect(config).toEqual({
sslKeyPath: '/path/to/key',
sslCertPath: '/path/to/cert',
});
});

it('returns null and prints error when --https option is not followed by key and cert paths', () => {
context.input.argv = ['--https'];

const config = getHttpsConfig(context);

expect(config).toEqual(null);
expect(context.print.error).toHaveBeenCalled();
});
});
8 changes: 7 additions & 1 deletion packages/amplify-util-mock/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ export class APITest {
private userOverriddenSlots: string[] = [];
private searchableTables: string[] = [];

async start(context, port: number = MOCK_API_PORT, wsPort: number = MOCK_API_PORT) {
async start(
context,
port: number = MOCK_API_PORT,
wsPort: number = MOCK_API_PORT,
httpsConfig?: { sslKeyPath: string; sslCertPath: string },
) {
try {
context.amplify.addCleanUpTask(async (context) => {
await this.stop(context);
Expand All @@ -72,6 +77,7 @@ export class APITest {
this.appSyncSimulator = new AmplifyAppSyncSimulator({
port,
wsPort,
httpsConfig: httpsConfig,
});
await this.appSyncSimulator.start();
await this.resolverOverrideManager.start();
Expand Down
5 changes: 4 additions & 1 deletion packages/amplify-util-mock/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { APITest } from './api';
import { addMockDataToGitIgnore, addMockAPIResourcesToGitIgnore } from '../utils';
import { getMockConfig } from '../utils/mock-config-file';
import { getHttpsConfig } from '../utils/get-https-config';

export async function start(context) {
const testApi = new APITest();
try {
addMockDataToGitIgnore(context);
addMockAPIResourcesToGitIgnore(context);
const mockConfig = await getMockConfig(context);
await testApi.start(context, mockConfig.graphqlPort, mockConfig.graphqlPort);
const httpsConfig = getHttpsConfig(context);

await testApi.start(context, mockConfig.graphqlPort, mockConfig.graphqlPort, httpsConfig);
} catch (e) {
console.log(e);
// Sending term signal so we clean up after ourselves
Expand Down
29 changes: 29 additions & 0 deletions packages/amplify-util-mock/src/utils/get-https-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function getHttpsConfig(context): { sslKeyPath: string; sslCertPath: string } | null {
if (!context.input || !context.input.argv) {
return null;
}

const argv = context.input.argv;
const httpsIndex = argv.indexOf('--https');

if (httpsIndex !== -1) {
if (httpsIndex < argv.length - 2) {
const keyPath = argv[httpsIndex + 1];
const certPath = argv[httpsIndex + 2];
if (typeof keyPath === 'string' && typeof certPath === 'string') {
return { sslKeyPath: keyPath, sslCertPath: certPath };
} else {
context.print.error('\nThe provided paths for the SSL key and certificate are not valid.\n');
context.print.error('Please ensure you have entered the correct paths.\n');
}
} else {
context.print.error('\nThe --https option must be followed by the path to the SSL key and the path to the SSL certificate.\n');
context.print.error('Example: amplify mock api --https /path/to/key /path/to/cert\n');
context.print.error('In order to generate a key and certificate, you can use openssl:\n');
context.print.error('openssl req -nodes -new -x509 -keyout server.key -out server.cert\n');
context.print.error('Then, run the command again with the paths to the generated key and certificate.\n');
}
}

return null;
}