diff --git a/cli/src/services/session.service.ts b/cli/src/services/session.service.ts index c9eae671fd..0235b30a4c 100644 --- a/cli/src/services/session.service.ts +++ b/cli/src/services/session.service.ts @@ -3,6 +3,7 @@ import { access, constants, mkdir, readFile, unlink, writeFile } from 'node:fs/p import path from 'node:path'; import yaml from 'yaml'; import { ImmichApi } from './api.service'; + class LoginError extends Error { constructor(message: string) { super(message); @@ -14,14 +15,12 @@ class LoginError extends Error { } export class SessionService { - readonly configDirectory!: string; - readonly authPath!: string; - - constructor(configDirectory: string) { - this.configDirectory = configDirectory; - this.authPath = path.join(configDirectory, '/auth.yml'); + private get authPath() { + return path.join(this.configDirectory, '/auth.yml'); } + constructor(private configDirectory: string) {} + async connect(): Promise { let instanceUrl = process.env.IMMICH_INSTANCE_URL; let apiKey = process.env.IMMICH_API_KEY; @@ -48,6 +47,8 @@ export class SessionService { } } + instanceUrl = await this.resolveApiEndpoint(instanceUrl); + const api = new ImmichApi(instanceUrl, apiKey); const pingResponse = await api.pingServer().catch((error) => { @@ -62,7 +63,9 @@ export class SessionService { } async login(instanceUrl: string, apiKey: string): Promise { - console.log('Logging in...'); + console.log(`Logging in to ${instanceUrl}`); + + instanceUrl = await this.resolveApiEndpoint(instanceUrl); const api = new ImmichApi(instanceUrl, apiKey); @@ -83,7 +86,7 @@ export class SessionService { await writeFile(this.authPath, yaml.stringify({ instanceUrl, apiKey }), { mode: 0o600 }); - console.log('Wrote auth info to ' + this.authPath); + console.log(`Wrote auth info to ${this.authPath}`); return api; } @@ -98,4 +101,18 @@ export class SessionService { console.log('Successfully logged out'); } + + private async resolveApiEndpoint(instanceUrl: string): Promise { + const wellKnownUrl = new URL('.well-known/immich', instanceUrl); + try { + const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); + const endpoint = new URL(wellKnown.api.endpoint, instanceUrl).toString(); + if (endpoint !== instanceUrl) { + console.debug(`Discovered API at ${endpoint}`); + } + return endpoint; + } catch { + return instanceUrl; + } + } } diff --git a/e2e/src/cli/specs/login.e2e-spec.ts b/e2e/src/cli/specs/login.e2e-spec.ts index e3140ecea9..aa27bec63e 100644 --- a/e2e/src/cli/specs/login.e2e-spec.ts +++ b/e2e/src/cli/specs/login.e2e-spec.ts @@ -29,12 +29,12 @@ describe(`immich login-key`, () => { expect(exitCode).toBe(1); }); - it('should login', async () => { + it('should login and save auth.yml with 600', async () => { const admin = await apiUtils.adminSetup(); const key = await apiUtils.createApiKey(admin.accessToken); const { stdout, stderr, exitCode } = await immichCli(['login-key', app, `${key.secret}`]); expect(stdout.split('\n')).toEqual([ - 'Logging in...', + 'Logging in to http://127.0.0.1:2283/api', 'Logged in as admin@immich.cloud', 'Wrote auth info to /tmp/immich/auth.yml', ]); @@ -45,4 +45,18 @@ describe(`immich login-key`, () => { const mode = (stats.mode & 0o777).toString(8); expect(mode).toEqual('600'); }); + + it('should login without /api in the url', async () => { + const admin = await apiUtils.adminSetup(); + const key = await apiUtils.createApiKey(admin.accessToken); + const { stdout, stderr, exitCode } = await immichCli(['login-key', 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', + 'Logged in as admin@immich.cloud', + 'Wrote auth info to /tmp/immich/auth.yml', + ]); + expect(stderr).toBe(''); + expect(exitCode).toBe(0); + }); });