diff --git a/cli/src/commands/auth.ts b/cli/src/commands/auth.ts index 05f3d7953d..6675201a7b 100644 --- a/cli/src/commands/auth.ts +++ b/cli/src/commands/auth.ts @@ -3,12 +3,12 @@ import { existsSync } from 'node:fs'; import { mkdir, unlink } from 'node:fs/promises'; import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils'; -export const login = async (instanceUrl: string, apiKey: string, options: BaseOptions) => { - console.log(`Logging in to ${instanceUrl}`); +export const login = async (url: string, key: string, options: BaseOptions) => { + console.log(`Logging in to ${url}`); const { configDirectory: configDir } = options; - await connect(instanceUrl, apiKey); + await connect(url, key); const [error, userInfo] = await withError(getMyUserInfo()); if (error) { @@ -27,7 +27,7 @@ export const login = async (instanceUrl: string, apiKey: string, options: BaseOp } } - await writeAuthFile(configDir, { instanceUrl, apiKey }); + await writeAuthFile(configDir, { url, key }); console.log(`Wrote auth info to ${getAuthFilePath(configDir)}`); }; diff --git a/cli/src/index.ts b/cli/src/index.ts index bf7e13f445..c3da631b98 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -19,7 +19,7 @@ const program = new Command() .default(defaultConfigDirectory), ) .addOption(new Option('-u, --url [url]', 'Immich server URL').env('IMMICH_INSTANCE_URL')) - .addOption(new Option('-k, --key [apiKey]', 'Immich API key').env('IMMICH_API_KEY')); + .addOption(new Option('-k, --key [key]', 'Immich API key').env('IMMICH_API_KEY')); program .command('login') diff --git a/cli/src/utils.ts b/cli/src/utils.ts index fa70524854..c17ad69038 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -8,44 +8,42 @@ import yaml from 'yaml'; export interface BaseOptions { configDirectory: string; - apiKey?: string; - instanceUrl?: string; + key?: string; + url?: string; } -export interface AuthDto { - instanceUrl: string; - apiKey: string; -} +export type AuthDto = { url: string; key: string }; +type OldAuthDto = { instanceUrl: string; apiKey: string }; export const authenticate = async (options: BaseOptions): Promise => { - const { configDirectory: configDir, instanceUrl, apiKey } = options; + const { configDirectory: configDir, url, key } = options; // provided in command - if (instanceUrl && apiKey) { - await connect(instanceUrl, apiKey); + if (url && key) { + await connect(url, key); return; } - // fallback to file + // fallback to auth file const config = await readAuthFile(configDir); - await connect(config.instanceUrl, config.apiKey); + await connect(config.url, config.key); }; -export const connect = async (instanceUrl: string, apiKey: string): Promise => { - const wellKnownUrl = new URL('.well-known/immich', instanceUrl); +export const connect = async (url: string, key: string): Promise => { + const wellKnownUrl = new URL('.well-known/immich', url); try { const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); - const endpoint = new URL(wellKnown.api.endpoint, instanceUrl).toString(); - if (endpoint !== instanceUrl) { + const endpoint = new URL(wellKnown.api.endpoint, url).toString(); + if (endpoint !== url) { console.debug(`Discovered API at ${endpoint}`); } - instanceUrl = endpoint; + url = endpoint; } catch { // noop } - defaults.baseUrl = instanceUrl; - defaults.headers = { 'x-api-key': apiKey }; + defaults.baseUrl = url; + defaults.headers = { 'x-api-key': key }; const [error] = await withError(getMyUserInfo()); if (isHttpError(error)) { @@ -69,7 +67,12 @@ export const readAuthFile = async (dir: string) => { try { const data = await readFile(getAuthFilePath(dir)); // TODO add class-transform/validation - return yaml.parse(data.toString()) as AuthDto; + const auth = yaml.parse(data.toString()) as AuthDto | OldAuthDto; + const { instanceUrl, apiKey } = auth as OldAuthDto; + if (instanceUrl && apiKey) { + return { url: instanceUrl, key: apiKey }; + } + return auth as AuthDto; } catch (error: Error | any) { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { console.log('No auth file exists. Please login first.'); diff --git a/docs/docs/features/command-line-interface.md b/docs/docs/features/command-line-interface.md index 360b3c3728..d4c3a1d8ef 100644 --- a/docs/docs/features/command-line-interface.md +++ b/docs/docs/features/command-line-interface.md @@ -61,7 +61,7 @@ Options: Commands: upload [options] [paths...] Upload assets server-info Display server information - login-key [instanceUrl] [apiKey] Login using an API key + login [url] [key] Login using an API key logout Remove stored credentials help [command] display help for command ``` @@ -97,13 +97,13 @@ Note that the above options can read from environment variables as well. You begin by authenticating to your Immich server. ```bash -immich login-key [instanceUrl] [apiKey] +immich login [url] [key] ``` For instance, ```bash -immich login-key http://192.168.1.216:2283/api HFEJ38DNSDUEG +immich login http://192.168.1.216:2283/api HFEJ38DNSDUEG ``` This will store your credentials in a `auth.yml` file in the configuration directory which defaults to `~/.config/`. The directory can be set with the `-d` option or the environment variable `IMMICH_CONFIG_DIR`. Please keep the file secure, either by performing the logout command after you are done, or deleting it manually. diff --git a/e2e/src/cli/specs/login.e2e-spec.ts b/e2e/src/cli/specs/login.e2e-spec.ts index 42877221fc..0fb48188a2 100644 --- a/e2e/src/cli/specs/login.e2e-spec.ts +++ b/e2e/src/cli/specs/login.e2e-spec.ts @@ -2,25 +2,25 @@ import { stat } from 'node:fs/promises'; import { app, immichCli, utils } from 'src/utils'; import { beforeEach, describe, expect, it } from 'vitest'; -describe(`immich login-key`, () => { +describe(`immich login`, () => { beforeEach(async () => { await utils.resetDatabase(); }); it('should require a url', async () => { - const { stderr, exitCode } = await immichCli(['login-key']); + const { stderr, exitCode } = await immichCli(['login']); expect(stderr).toBe("error: missing required argument 'url'"); expect(exitCode).toBe(1); }); it('should require a key', async () => { - const { stderr, exitCode } = await immichCli(['login-key', app]); + const { stderr, exitCode } = await immichCli(['login', app]); expect(stderr).toBe("error: missing required argument 'key'"); expect(exitCode).toBe(1); }); it('should require a valid key', async () => { - const { stderr, exitCode } = await immichCli(['login-key', app, 'immich-is-so-cool']); + const { stderr, exitCode } = await immichCli(['login', app, 'immich-is-so-cool']); expect(stderr).toContain('Failed to connect to server'); expect(stderr).toContain('Invalid API key'); expect(stderr).toContain('401'); @@ -30,7 +30,7 @@ describe(`immich login-key`, () => { it('should login and save auth.yml with 600', async () => { const admin = await utils.adminSetup(); const key = await utils.createApiKey(admin.accessToken); - const { stdout, stderr, exitCode } = await immichCli(['login-key', app, `${key.secret}`]); + const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]); expect(stdout.split('\n')).toEqual([ 'Logging in to http://127.0.0.1:2283/api', 'Logged in as admin@immich.cloud', @@ -47,7 +47,7 @@ describe(`immich login-key`, () => { it('should login without /api in the url', async () => { const admin = await utils.adminSetup(); const key = await utils.createApiKey(admin.accessToken); - const { stdout, stderr, exitCode } = await immichCli(['login-key', app.replaceAll('/api', ''), `${key.secret}`]); + const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]); expect(stdout.split('\n')).toEqual([ 'Logging in to http://127.0.0.1:2283', 'Discovered API at http://127.0.0.1:2283/api', diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 8e56141bf7..6b538129a0 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -406,7 +406,7 @@ export const utils = { cliLogin: async (accessToken: string) => { const key = await utils.createApiKey(accessToken); - await immichCli(['login-key', app, `${key.secret}`]); + await immichCli(['login', app, `${key.secret}`]); return key.secret; }, };