diff --git a/README.md b/README.md index ba3bb13..e0c96ea 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,8 @@ mkdir -p .local cat > .local/steamkit-env.sh <<'EOF' STEAMKIT_USERNAME='your-watch-account' STEAMKIT_PASSWORD='your-watch-account-password' -# Use one of these only for the one-time session bootstrap when Steam Guard asks for it: +# Usually leave these unset and enter the code when scripts/steamkit_auth.sh prompts. +# For non-interactive use, set one of these only when Steam Guard asks for it: # STEAMKIT_AUTH_CODE='email-code' # STEAMKIT_TWO_FACTOR_CODE='authenticator-code' # Or approve the mobile prompt manually, then set: diff --git a/SKILL.md b/SKILL.md index a11abbe..3304dc8 100644 --- a/SKILL.md +++ b/SKILL.md @@ -148,7 +148,8 @@ Use a narrow SteamKit2 helper when available. It should: SteamKit auth handling: - keep `.local/steamkit-env.sh` and `.local/steamkit-session.json` local and uncommitted -- use `STEAMKIT_AUTH_CODE`, `STEAMKIT_TWO_FACTOR_CODE`, or `STEAMKIT_ACCEPT_MOBILE_CONFIRMATION=1` only for the bootstrap command +- prefer an interactive `scripts/steamkit_auth.sh` run for email or mobile guard challenges, because email codes are tied to the active login attempt +- use `STEAMKIT_AUTH_CODE`, `STEAMKIT_TWO_FACTOR_CODE`, or `STEAMKIT_ACCEPT_MOBILE_CONFIRMATION=1` only for non-interactive bootstrap commands - remove one-time guard values from `.local/steamkit-env.sh` after `scripts/steamkit_auth.sh` succeeds - if routine checks fail because the refresh token expired or was revoked, ask the user to re-run `scripts/steamkit_auth.sh` with a fresh Steam Guard approval diff --git a/scripts/steamkit_auth.sh b/scripts/steamkit_auth.sh index 5d4a810..322dcdd 100755 --- a/scripts/steamkit_auth.sh +++ b/scripts/steamkit_auth.sh @@ -8,6 +8,7 @@ ENV_FILE="${STEAMKIT_ENV_FILE:-$REPO_DIR/.local/steamkit-env.sh}" SESSION_FILE="${STEAMKIT_SESSION_FILE:-$REPO_DIR/.local/steamkit-session.json}" if [ -f "$ENV_FILE" ]; then + printf '%s\n' "Loading SteamKit environment from $ENV_FILE" >&2 # shellcheck disable=SC1090 . "$ENV_FILE" fi @@ -33,6 +34,7 @@ fi mkdir -p "$(dirname "$SESSION_FILE")" +printf '%s\n' "Starting SteamKit auth bootstrap. Waiting for Steam to request any required Guard confirmation..." >&2 dotnet run --project "$PROJECT_DIR" -- --auth-session-out "$SESSION_FILE" --session-file "$SESSION_FILE" chmod 600 "$SESSION_FILE" printf '%s\n' "Saved SteamKit session to $SESSION_FILE" diff --git a/tests/test_check_steamkit_pics.py b/tests/test_check_steamkit_pics.py index b7ebfb3..3bad96e 100644 --- a/tests/test_check_steamkit_pics.py +++ b/tests/test_check_steamkit_pics.py @@ -125,6 +125,7 @@ class CheckSteamKitPicsTests(unittest.TestCase): { "PATH": f"{fake_bin}:{env['PATH']}", "STEAMKIT_ENV_FILE": str(tmp_path / "missing-env.sh"), + "STEAMKIT_SESSION_FILE": str(tmp_path / "missing-session.json"), } ) env.pop("STEAMKIT_USERNAME", None) @@ -483,7 +484,7 @@ class SteamKitAuthScriptTests(unittest.TestCase): check=False, ) - self.assertEqual("", result.stderr) + self.assertIn("Starting SteamKit auth bootstrap", result.stderr) self.assertEqual(0, result.returncode) self.assertTrue(session_file.exists()) dotnet_args = log_path.read_text(encoding="utf-8") diff --git a/tools/steamkit-pics/Program.cs b/tools/steamkit-pics/Program.cs index aa9da79..14561a4 100644 --- a/tools/steamkit-pics/Program.cs +++ b/tools/steamkit-pics/Program.cs @@ -213,9 +213,11 @@ static class AuthClient } }); + Console.Error.WriteLine("Connecting to Steam..."); steamClient.Connect(); await CallbackPump.UntilAsync(manager, connected.Task, disconnected.Task, cancellationToken); + Console.Error.WriteLine("Connected. Starting Steam credential auth session..."); var authTask = steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new AuthSessionDetails { Username = username, @@ -230,6 +232,7 @@ static class AuthClient await CallbackPump.UntilAsync(manager, authTask, disconnected.Task, cancellationToken); var authSession = await authTask; + Console.Error.WriteLine("Auth session created. Waiting for Steam Guard confirmation or token response..."); var pollTask = authSession.PollingWaitForResultAsync(cancellationToken); await CallbackPump.UntilAsync(manager, pollTask, disconnected.Task, cancellationToken); var result = await pollTask; @@ -261,7 +264,16 @@ sealed class EnvAuthenticator(string? emailCode, string? deviceCode, bool accept return Task.FromResult(deviceCode); } - throw new InvalidOperationException("Steam mobile authenticator code required. Set STEAMKIT_TWO_FACTOR_CODE and rerun scripts/steamkit_auth.sh."); + if (!Console.IsInputRedirected) + { + return Task.FromResult(ReadRequiredSecret( + previousCodeWasIncorrect + ? "Steam mobile authenticator code was rejected. Enter the current Steam mobile authenticator code: " + : "Enter the current Steam mobile authenticator code: " + )); + } + + throw new InvalidOperationException("Steam mobile authenticator code required. Set STEAMKIT_TWO_FACTOR_CODE or run scripts/steamkit_auth.sh from an interactive terminal."); } public Task GetEmailCodeAsync(string email, bool previousCodeWasIncorrect) @@ -271,10 +283,46 @@ sealed class EnvAuthenticator(string? emailCode, string? deviceCode, bool accept return Task.FromResult(emailCode); } - throw new InvalidOperationException($"Steam Guard email code required for {email}. Set STEAMKIT_AUTH_CODE and rerun scripts/steamkit_auth.sh."); + if (!Console.IsInputRedirected) + { + return Task.FromResult(ReadRequiredSecret( + previousCodeWasIncorrect + ? $"Steam Guard email code for {email} was rejected. Enter the latest Steam Guard email code: " + : $"Enter the Steam Guard email code sent to {email}: " + )); + } + + throw new InvalidOperationException($"Steam Guard email code required for {email}. Set STEAMKIT_AUTH_CODE or run scripts/steamkit_auth.sh from an interactive terminal."); } - public Task AcceptDeviceConfirmationAsync() => Task.FromResult(acceptMobileConfirmation); + public Task AcceptDeviceConfirmationAsync() + { + if (acceptMobileConfirmation) + { + return Task.FromResult(true); + } + + if (!Console.IsInputRedirected) + { + Console.Error.Write("Approve the Steam mobile confirmation, then press Enter to continue."); + _ = Console.ReadLine(); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + static string ReadRequiredSecret(string prompt) + { + Console.Error.Write(prompt); + var value = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException("Steam Guard code was empty."); + } + + return value.Trim(); + } } static class CallbackPump @@ -292,7 +340,9 @@ static class CallbackPump throw new InvalidOperationException(await disconnected); } - await manager.RunWaitCallbackAsync(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + manager.RunWaitCallbacks(TimeSpan.FromMilliseconds(250)); + await Task.Yield(); } await primary;