diff --git a/cli/package-lock.json b/cli/package-lock.json index d564d1c28c..4a839dd9e5 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -3810,6 +3810,15 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -4182,15 +4191,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8566,6 +8566,12 @@ "get-func-name": "^2.0.1" } }, + "lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true + }, "magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -8834,14 +8840,6 @@ "requires": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true - } } }, "path-type": { diff --git a/cli/src/commands/base-command.ts b/cli/src/commands/base-command.ts index 9218da6026..30da229418 100644 --- a/cli/src/commands/base-command.ts +++ b/cli/src/commands/base-command.ts @@ -8,11 +8,11 @@ export abstract class BaseCommand { protected user!: UserResponseDto; protected serverVersion!: ServerVersionResponseDto; - constructor(options: { config?: string }) { - if (!options.config) { + constructor(options: { configDirectory?: string }) { + if (!options.configDirectory) { throw new Error('Config directory is required'); } - this.sessionService = new SessionService(options.config); + this.sessionService = new SessionService(options.configDirectory); } public async connect(): Promise { diff --git a/cli/src/services/session.service.ts b/cli/src/services/session.service.ts index 05df2ec6e3..940cc94d25 100644 --- a/cli/src/services/session.service.ts +++ b/cli/src/services/session.service.ts @@ -82,7 +82,7 @@ export class SessionService { } } - await writeFile(this.authPath, yaml.stringify({ instanceUrl, apiKey })); + await writeFile(this.authPath, yaml.stringify({ instanceUrl, apiKey }), { mode: 0o600 }); console.log('Wrote auth info to ' + this.authPath); diff --git a/cli/test/cli-test-utils.ts b/cli/test/cli-test-utils.ts index 405b7794df..cce81c5ffd 100644 --- a/cli/test/cli-test-utils.ts +++ b/cli/test/cli-test-utils.ts @@ -7,7 +7,7 @@ export const TEST_AUTH_FILE = path.join(TEST_CONFIG_DIR, 'auth.yml'); export const TEST_IMMICH_INSTANCE_URL = 'https://test/api'; export const TEST_IMMICH_API_KEY = 'pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg'; -export const CLI_BASE_OPTIONS = { config: TEST_CONFIG_DIR }; +export const CLI_BASE_OPTIONS = { configDirectory: TEST_CONFIG_DIR }; export const setup = async () => { const api = new ImmichApi(process.env.IMMICH_INSTANCE_URL as string, ''); diff --git a/cli/test/e2e/login-key.e2e-spec.ts b/cli/test/e2e/login-key.e2e-spec.ts index 8b215def75..b2c2dcacbb 100644 --- a/cli/test/e2e/login-key.e2e-spec.ts +++ b/cli/test/e2e/login-key.e2e-spec.ts @@ -1,6 +1,8 @@ import { restoreTempFolder, testApp } from '@test-utils'; -import { CLI_BASE_OPTIONS, setup, spyOnConsole } from 'test/cli-test-utils'; +import { CLI_BASE_OPTIONS, TEST_AUTH_FILE, deleteAuthFile, setup, spyOnConsole } from 'test/cli-test-utils'; +import { readFile, stat } from 'node:fs/promises'; import { LoginCommand } from '../../src/commands/login'; +import yaml from 'yaml'; describe(`login-key (e2e)`, () => { let apiKey: string; @@ -20,6 +22,7 @@ describe(`login-key (e2e)`, () => { afterAll(async () => { await testApp.teardown(); await restoreTempFolder(); + deleteAuthFile(); }); beforeEach(async () => { @@ -28,6 +31,8 @@ describe(`login-key (e2e)`, () => { const api = await setup(); apiKey = api.apiKey; + + deleteAuthFile(); }); it('should error when providing an invalid API key', async () => { @@ -39,4 +44,23 @@ describe(`login-key (e2e)`, () => { it('should log in when providing the correct API key', async () => { await new LoginCommand(CLI_BASE_OPTIONS).run(instanceUrl, apiKey); }); + + it('should create an auth file when logging in', async () => { + await new LoginCommand(CLI_BASE_OPTIONS).run(instanceUrl, apiKey); + + const data: string = await readFile(TEST_AUTH_FILE, 'utf8'); + const parsedConfig = yaml.parse(data); + + expect(parsedConfig).toEqual(expect.objectContaining({ instanceUrl, apiKey })); + }); + + it('should create an auth file with chmod 600', async () => { + await new LoginCommand(CLI_BASE_OPTIONS).run(instanceUrl, apiKey); + + const stats = await stat(TEST_AUTH_FILE); + + const mode = (stats.mode & 0o777).toString(8); + + expect(mode).toEqual('600'); + }); });