chore(cli): rename commands (#8200)

* chore(cli): rename login command

* chore: rename key/url
This commit is contained in:
Jason Rasmussen 2024-03-22 15:09:04 -04:00 committed by GitHub
parent 5b7417bf64
commit 75aa8e6621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 37 additions and 34 deletions

View File

@ -3,12 +3,12 @@ import { existsSync } from 'node:fs';
import { mkdir, unlink } from 'node:fs/promises'; import { mkdir, unlink } from 'node:fs/promises';
import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils'; import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils';
export const login = async (instanceUrl: string, apiKey: string, options: BaseOptions) => { export const login = async (url: string, key: string, options: BaseOptions) => {
console.log(`Logging in to ${instanceUrl}`); console.log(`Logging in to ${url}`);
const { configDirectory: configDir } = options; const { configDirectory: configDir } = options;
await connect(instanceUrl, apiKey); await connect(url, key);
const [error, userInfo] = await withError(getMyUserInfo()); const [error, userInfo] = await withError(getMyUserInfo());
if (error) { 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)}`); console.log(`Wrote auth info to ${getAuthFilePath(configDir)}`);
}; };

View File

@ -19,7 +19,7 @@ const program = new Command()
.default(defaultConfigDirectory), .default(defaultConfigDirectory),
) )
.addOption(new Option('-u, --url [url]', 'Immich server URL').env('IMMICH_INSTANCE_URL')) .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 program
.command('login') .command('login')

View File

@ -8,44 +8,42 @@ import yaml from 'yaml';
export interface BaseOptions { export interface BaseOptions {
configDirectory: string; configDirectory: string;
apiKey?: string; key?: string;
instanceUrl?: string; url?: string;
} }
export interface AuthDto { export type AuthDto = { url: string; key: string };
instanceUrl: string; type OldAuthDto = { instanceUrl: string; apiKey: string };
apiKey: string;
}
export const authenticate = async (options: BaseOptions): Promise<void> => { export const authenticate = async (options: BaseOptions): Promise<void> => {
const { configDirectory: configDir, instanceUrl, apiKey } = options; const { configDirectory: configDir, url, key } = options;
// provided in command // provided in command
if (instanceUrl && apiKey) { if (url && key) {
await connect(instanceUrl, apiKey); await connect(url, key);
return; return;
} }
// fallback to file // fallback to auth file
const config = await readAuthFile(configDir); 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<void> => { export const connect = async (url: string, key: string): Promise<void> => {
const wellKnownUrl = new URL('.well-known/immich', instanceUrl); const wellKnownUrl = new URL('.well-known/immich', url);
try { try {
const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); const wellKnown = await fetch(wellKnownUrl).then((response) => response.json());
const endpoint = new URL(wellKnown.api.endpoint, instanceUrl).toString(); const endpoint = new URL(wellKnown.api.endpoint, url).toString();
if (endpoint !== instanceUrl) { if (endpoint !== url) {
console.debug(`Discovered API at ${endpoint}`); console.debug(`Discovered API at ${endpoint}`);
} }
instanceUrl = endpoint; url = endpoint;
} catch { } catch {
// noop // noop
} }
defaults.baseUrl = instanceUrl; defaults.baseUrl = url;
defaults.headers = { 'x-api-key': apiKey }; defaults.headers = { 'x-api-key': key };
const [error] = await withError(getMyUserInfo()); const [error] = await withError(getMyUserInfo());
if (isHttpError(error)) { if (isHttpError(error)) {
@ -69,7 +67,12 @@ export const readAuthFile = async (dir: string) => {
try { try {
const data = await readFile(getAuthFilePath(dir)); const data = await readFile(getAuthFilePath(dir));
// TODO add class-transform/validation // 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) { } catch (error: Error | any) {
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { if (error.code === 'ENOENT' || error.code === 'ENOTDIR') {
console.log('No auth file exists. Please login first.'); console.log('No auth file exists. Please login first.');

View File

@ -61,7 +61,7 @@ Options:
Commands: Commands:
upload [options] [paths...] Upload assets upload [options] [paths...] Upload assets
server-info Display server information 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 logout Remove stored credentials
help [command] display help for command 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. You begin by authenticating to your Immich server.
```bash ```bash
immich login-key [instanceUrl] [apiKey] immich login [url] [key]
``` ```
For instance, For instance,
```bash ```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. 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.

View File

@ -2,25 +2,25 @@ import { stat } from 'node:fs/promises';
import { app, immichCli, utils } from 'src/utils'; import { app, immichCli, utils } from 'src/utils';
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
describe(`immich login-key`, () => { describe(`immich login`, () => {
beforeEach(async () => { beforeEach(async () => {
await utils.resetDatabase(); await utils.resetDatabase();
}); });
it('should require a url', async () => { 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(stderr).toBe("error: missing required argument 'url'");
expect(exitCode).toBe(1); expect(exitCode).toBe(1);
}); });
it('should require a key', async () => { 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(stderr).toBe("error: missing required argument 'key'");
expect(exitCode).toBe(1); expect(exitCode).toBe(1);
}); });
it('should require a valid key', async () => { 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('Failed to connect to server');
expect(stderr).toContain('Invalid API key'); expect(stderr).toContain('Invalid API key');
expect(stderr).toContain('401'); expect(stderr).toContain('401');
@ -30,7 +30,7 @@ describe(`immich login-key`, () => {
it('should login and save auth.yml with 600', async () => { it('should login and save auth.yml with 600', async () => {
const admin = await utils.adminSetup(); const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken); 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([ expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2283/api', 'Logging in to http://127.0.0.1:2283/api',
'Logged in as admin@immich.cloud', 'Logged in as admin@immich.cloud',
@ -47,7 +47,7 @@ describe(`immich login-key`, () => {
it('should login without /api in the url', async () => { it('should login without /api in the url', async () => {
const admin = await utils.adminSetup(); const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken); 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([ expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2283', 'Logging in to http://127.0.0.1:2283',
'Discovered API at http://127.0.0.1:2283/api', 'Discovered API at http://127.0.0.1:2283/api',

View File

@ -406,7 +406,7 @@ export const utils = {
cliLogin: async (accessToken: string) => { cliLogin: async (accessToken: string) => {
const key = await utils.createApiKey(accessToken); const key = await utils.createApiKey(accessToken);
await immichCli(['login-key', app, `${key.secret}`]); await immichCli(['login', app, `${key.secret}`]);
return key.secret; return key.secret;
}, },
}; };