From df600b9eddaaa076581074d9cb2a3ce1449d48b6 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Sun, 20 Apr 2025 10:08:59 -0700 Subject: [PATCH] feat(auth): Add `--token` parameter to `auth login` Allow logging into the CLI with a token for non-interactive environments. --- .../lib/src/commands/auth/auth_command.dart | 2 + .../lib/src/commands/auth/login_command.dart | 40 +++++++++++++++++++ .../lib/src/commands/auth/whoami_command.dart | 27 +++++++++++++ apps/cli/lib/src/commands/celest_command.dart | 4 +- 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 apps/cli/lib/src/commands/auth/whoami_command.dart diff --git a/apps/cli/lib/src/commands/auth/auth_command.dart b/apps/cli/lib/src/commands/auth/auth_command.dart index 7bc1dd004..5d4e551a3 100644 --- a/apps/cli/lib/src/commands/auth/auth_command.dart +++ b/apps/cli/lib/src/commands/auth/auth_command.dart @@ -1,6 +1,7 @@ import 'package:celest_cli/src/commands/auth/login_command.dart'; import 'package:celest_cli/src/commands/auth/logout_command.dart'; import 'package:celest_cli/src/commands/auth/token_command.dart'; +import 'package:celest_cli/src/commands/auth/whoami_command.dart'; import 'package:celest_cli/src/commands/celest_command.dart'; final class AuthCommand extends CelestCommand { @@ -8,6 +9,7 @@ final class AuthCommand extends CelestCommand { addSubcommand(LoginCommand()); addSubcommand(LogoutCommand()); addSubcommand(TokenCommand()); + addSubcommand(WhoamiCommand()); } @override diff --git a/apps/cli/lib/src/commands/auth/login_command.dart b/apps/cli/lib/src/commands/auth/login_command.dart index ff375c6c4..484b620bb 100644 --- a/apps/cli/lib/src/commands/auth/login_command.dart +++ b/apps/cli/lib/src/commands/auth/login_command.dart @@ -1,20 +1,60 @@ +import 'package:cedar/cedar.dart'; import 'package:celest_auth/celest_auth.dart'; import 'package:celest_cli/src/commands/auth/authenticate.dart'; import 'package:celest_cli/src/commands/auth/cli_auth.dart'; import 'package:celest_cli/src/commands/celest_command.dart'; import 'package:celest_cli/src/context.dart'; +import 'package:celest_cli/src/exceptions.dart'; +import 'package:corks_cedar/corks_cedar.dart'; final class LoginCommand extends CelestCommand with Authenticate { + LoginCommand() { + argParser.addOption( + 'token', + help: 'Use a token to authenticate instead of the interactive CLI flow.', + ); + } + @override String get name => 'login'; @override String get description => 'Login to Celest Cloud.'; + String? get token => argResults?['token'] as String?; + + Future _loginWithToken(String token) async { + final cork = CedarCork.parse(token); + final userId = switch (cork.claims) { + Entity(parents: [EntityUid(type: 'Celest::User', :final id)]) => id, + _ => throw CliException('Invalid token passed with --token flag.'), + }; + auth.localStorage.write('userId', userId); + await auth.secureStorage.write('cork', token); + + final authState = await auth.init(); + logger.finest('Auth state: $authState'); + if (authState case Authenticated(:final user)) { + cliLogger.success( + 'You have been logged in as: ${user.primaryEmail?.email}', + ); + return 0; + } + + // Failed to authenticate with the token. + auth.localStorage.delete('userId'); + await auth.secureStorage.delete('cork'); + throw CliException('Failed to authenticate with token.'); + } + @override Future run() async { await super.run(); + if (token case final token?) { + return _loginWithToken(token); + } + final authState = await auth.init(); logger.finest('Auth state: $authState'); switch (authState) { diff --git a/apps/cli/lib/src/commands/auth/whoami_command.dart b/apps/cli/lib/src/commands/auth/whoami_command.dart new file mode 100644 index 000000000..40593c9d7 --- /dev/null +++ b/apps/cli/lib/src/commands/auth/whoami_command.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:celest_cli/src/commands/auth/authenticate.dart'; +import 'package:celest_cli/src/commands/celest_command.dart'; + +final class WhoamiCommand extends CelestCommand with Authenticate { + @override + String get name => 'whoami'; + + @override + String get description => 'Print the current authenticated user\'s info.'; + + @override + Future run() async { + await super.run(); + + final user = await assertAuthenticated(); + if (jsonOutput) { + stdout.write(jsonEncode(user.toJson())); + } else { + stdout.writeln(user.toString()); + } + + return 0; + } +} diff --git a/apps/cli/lib/src/commands/celest_command.dart b/apps/cli/lib/src/commands/celest_command.dart index 009f96e7d..cc7ab1e9e 100644 --- a/apps/cli/lib/src/commands/celest_command.dart +++ b/apps/cli/lib/src/commands/celest_command.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; import 'package:celest_cli/src/cli/cli_runtime.dart'; +import 'package:celest_cli/src/commands/auth/auth_command.dart'; import 'package:celest_cli/src/context.dart'; import 'package:celest_cli/src/models.dart'; import 'package:celest_cli/src/releases/celest_release_info.dart'; @@ -124,7 +125,8 @@ abstract base class CelestCommand extends Command { 'cli', properties: { 'command': name, - 'args': argResults!.arguments.join(' '), + // Don't record args for auth commands, which may contain tokens. + if (parent is! AuthCommand) 'args': argResults!.arguments.join(' '), 'environment': kCliEnvironment, 'version': version, 'sdk_version': Sdk.current.version.toString(),