Skip to content

Commit

Permalink
EACCES: Permission Denied Errors (#2468)
Browse files Browse the repository at this point in the history
* EACCES errors

* update message

* changeset

* write to outputs with mode = 600

* add console statement for testing

* testing

* added error messaging

* testing

* removed testing console statements

* changed message

* updated error catching

* added tests to profile-controller and handling for windows users

* removed windows handling

* changed chmod to chmodSync

* test for client config writer

---------

Co-authored-by: Vieltojarvi <[email protected]>
  • Loading branch information
ShadowCat567 and Vieltojarvi authored Feb 5, 2025
1 parent 8f04c9f commit de90deb
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .changeset/chatty-jars-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@aws-amplify/client-config': patch
'@aws-amplify/backend-cli': patch
---

Added catch blocks for EACCES Errors
60 changes: 60 additions & 0 deletions packages/cli/src/commands/configure/profile_controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ProfileController } from './profile_controller.js';
import assert from 'node:assert';
import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader';
import { EOL } from 'node:os';
import { chmodSync } from 'node:fs';
import { AmplifyUserError } from '@aws-amplify/platform-core';

const testAccessKeyId = 'testAccessKeyId';
const testSecretAccessKey = 'testSecretAccessKey';
Expand Down Expand Up @@ -218,6 +220,64 @@ void describe('profile controller', () => {
);
});

void it('throws error if config file already exists and is missing read permissions', async () => {
if (process.platform.startsWith('win')) {
// Windows does not have the same behavior when files are missing permissions
return;
}

const expectedErr = new AmplifyUserError('PermissionsError', {
message: `You do not have the permissions to read this file: ${configFilePath}.`,
resolution: `Ensure that you have the right permissions to read from ${configFilePath}.`,
});
chmodSync(configFilePath, 0o000);
await assert.rejects(
async () =>
await profileController.createOrAppendAWSFiles({
profile: testProfile2,
region: testRegion2,
accessKeyId: testAccessKeyId2,
secretAccessKey: testSecretAccessKey2,
}),
(error: AmplifyUserError) => {
assert.strictEqual(error.name, expectedErr.name);
assert.strictEqual(error.message, expectedErr.message);
assert.strictEqual(error.resolution, expectedErr.resolution);
return true;
}
);
});

void it('throws error if config file already exists and is missing write permissions', async () => {
if (process.platform.startsWith('win')) {
// Windows does not have the same behavior when files are missing permissions
return;
}

const expectedErr = new AmplifyUserError('PermissionsError', {
message: `You do not have the permissions to write to this file: ${configFilePath}`,
resolution: `Ensure that you have the right permissions to write to ${configFilePath}.`,
});

chmodSync(configFilePath, 0o444);

await assert.rejects(
async () =>
await profileController.createOrAppendAWSFiles({
profile: testProfile2,
region: testRegion2,
accessKeyId: testAccessKeyId2,
secretAccessKey: testSecretAccessKey2,
}),
(error: AmplifyUserError) => {
assert.strictEqual(error.name, expectedErr.name);
assert.strictEqual(error.message, expectedErr.message);
assert.strictEqual(error.resolution, expectedErr.resolution);
return true;
}
);
});

void it('creates directory if does not exist', async () => {
// delete directory
await fs.rm(testDir, { recursive: true, force: true });
Expand Down
32 changes: 30 additions & 2 deletions packages/cli/src/commands/configure/profile_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EOL } from 'os';
import fs from 'fs/promises';
import path from 'path';
import { existsSync } from 'fs';
import { AmplifyUserError } from '@aws-amplify/platform-core';

/**
* Options for the profile configuration.
Expand Down Expand Up @@ -77,7 +78,23 @@ export class ProfileController {
: `[profile ${options.profile}]${EOL}`;
configData += `region = ${options.region}${EOL}`;

await fs.appendFile(filePath, configData, { mode: '600' });
try {
await fs.appendFile(filePath, configData, { mode: '600' });
} catch (err) {
const error = err as Error;
if (error.message.includes('EACCES')) {
throw new AmplifyUserError(
'PermissionsError',
{
message: `You do not have the permissions to write to this file: ${filePath}`,
resolution: `Ensure that you have the right permissions to write to ${filePath}.`,
},
error
);
} else {
throw error;
}
}

// validate after write. It is to ensure this function is compatible with the current AWS format.
const profileData = await loadSharedConfigFiles({
Expand Down Expand Up @@ -135,7 +152,18 @@ export class ProfileController {
// file doesn't exists
return true;
}
throw err;
if (error.message.includes('EACCES')) {
throw new AmplifyUserError(
'PermissionsError',
{
message: `You do not have the permissions to read this file: ${filePath}.`,
resolution: `Ensure that you have the right permissions to read from ${filePath}.`,
},
error
);
} else {
throw err;
}
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '../client-config-types/client_config.js';
import { ClientConfigFormatterLegacy } from './client_config_formatter_legacy.js';
import { randomUUID } from 'crypto';
import { AmplifyUserError } from '@aws-amplify/platform-core';

void describe('client config writer', () => {
const sampleRegion = 'test_region';
Expand Down Expand Up @@ -180,4 +181,38 @@ void describe('client config writer', () => {
ClientConfigFormat.JSON
);
});

void it('throws an error when targetFile exists and is missing write permissions', async () => {
const outDir = '/foo/bar';
const targetFile = '/foo/bar/baz';
const format = ClientConfigFormat.MJS;
const expectedErr = new AmplifyUserError('PermissionsError', {
message: `You do not have the permissions to write to this file: ${targetFile}`,
resolution: `Ensure that you have the right permissions to write to ${targetFile}.`,
});

fspMock.writeFile.mock.mockImplementationOnce(() =>
Promise.reject(new Error('EACCES: Permission denied'))
);
pathResolverMock.mock.mockImplementation(() => Promise.resolve(targetFile));
nameResolverMock.mock.mockImplementation(
() => ClientConfigFileBaseName.DEFAULT
);

await assert.rejects(
async () =>
await clientConfigWriter.writeClientConfig(
clientConfig,
DEFAULT_CLIENT_CONFIG_VERSION,
outDir,
format
),
(error: AmplifyUserError) => {
assert.strictEqual(error.name, expectedErr.name);
assert.strictEqual(error.message, expectedErr.message);
assert.strictEqual(error.resolution, expectedErr.resolution);
return true;
}
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
GenerateClientConfigToFileResult,
} from '../client-config-types/client_config.js';
import { ClientConfigFormatter } from './client_config_formatter.js';
import { AmplifyUserError } from '@aws-amplify/platform-core';

export type ClientConfigPathResolver = (
fileName: ClientConfigFileBaseName,
Expand Down Expand Up @@ -47,7 +48,24 @@ export class ClientConfigWriter {
format
);
const fileContent = this.formatter.format(clientConfig, format);
await this.fsp.writeFile(targetPath, fileContent);

try {
await this.fsp.writeFile(targetPath, fileContent);
} catch (err) {
const error = err as Error;
if (error.message.includes('EACCES')) {
throw new AmplifyUserError(
'PermissionsError',
{
message: `You do not have the permissions to write to this file: ${targetPath}`,
resolution: `Ensure that you have the right permissions to write to ${targetPath}.`,
},
error
);
} else {
throw error;
}
}

return {
filesWritten: [path.relative(process.cwd(), targetPath)],
Expand Down

0 comments on commit de90deb

Please sign in to comment.