Skip to content

feat: Allow sign in with Steam Guard TOTP code#1241

Open
peanut250 wants to merge 7 commits into
utkarshdalal:masterfrom
peanut250:steam-guard-totp-code-support
Open

feat: Allow sign in with Steam Guard TOTP code#1241
peanut250 wants to merge 7 commits into
utkarshdalal:masterfrom
peanut250:steam-guard-totp-code-support

Conversation

@peanut250
Copy link
Copy Markdown

@peanut250 peanut250 commented Apr 17, 2026

Description

Allows signing in via Steam Guard TOTP code for those who have extracted the secrets for use in Bitwarden or other authenticators.

Recording

gamenative_steam_guard_totp_demo.webm

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by cubic

Add sign-in support for Steam Guard TOTP codes from third-party authenticators. Users can switch from device confirmation to entering a code; spacing is improved on wide layouts.

  • New Features

    • New “Or use 2-factor auth code” button on the 2FA screen; added steam_2fa_use_guard_totp with translations across locales.
    • Choosing TOTP restarts login, skips device confirmation, and prompts for a code; choice resets after one use.
  • Refactors

    • Extracted SteamAuthenticator to handle email/device/TOTP flows and update UserLoginState; ViewModel exposes useGuardTotp().
    • Threaded onUseGuardTotp from UserLoginScreen to TwoFactorAuthScreen.

Written for commit e9d520f. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added an option on the login two-factor screen to use a Steam authenticator (TOTP) code.
  • UI

    • Two-factor screen shows a new button/link "Or use 2-factor auth code" and previews updated.
    • Users can choose authenticator-based 2FA when prompted during login.
  • Localization

    • New localized text for the authenticator option across multiple languages.
  • Chores

    • Two-factor handling centralized behind a shared authenticator implementation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new public SteamAuthenticator implementing IAuthenticator, moves inline authenticator logic out of UserLoginViewModel into that class, exposes useGuardTotp() on the view model, threads a UI callback to switch to TOTP entry, and adds steam_2fa_use_guard_totp string resources across locales.

Changes

Steam 2FA flow (single cohesive cohort)

Layer / File(s) Summary
Authenticator contract & implementation
app/src/main/java/app/gamenative/service/SteamAuthenticator.kt
New public SteamAuthenticator : IAuthenticator using MutableStateFlow<UserLoginState>, a Channel<String> for submitted codes, and a CoroutineScope. Implements acceptDeviceConfirmation(): CompletableFuture<Boolean>, getDeviceCode(previousCodeWasIncorrect: Boolean): CompletableFuture<String>, and getEmailCode(email: String?, previousCodeWasIncorrect: Boolean): CompletableFuture<String>. Adds public mutable useGuardTotp: Boolean.
ViewModel wiring and API
app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt
Replaced inline IAuthenticator with a SteamAuthenticator instance (constructed from _loginState, submitChannel, viewModelScope). Added fun useGuardTotp() which sets authenticator.useGuardTotp = true and immediately calls SteamService.startLoginWithCredentials(...) with the same authenticator.
UI callback and two-factor UX
app/src/main/java/app/gamenative/ui/screen/login/TwoFactorAuthScreen.kt, app/src/main/java/app/gamenative/ui/screen/login/UserLoginScreen.kt
Threaded new onUseGuardTotp: () -> Unit through UserLoginScreenUserLoginScreenContentTwoFactorAuthScreenContent. TwoFactorAuthScreenContent shows a TextButton in the LoginResult.DeviceConfirm branch which calls onUseGuardTotp. Updated previews to provide a no-op callback.
String resources / localization
app/src/main/res/values*/strings.xml (multiple locales)
Added steam_2fa_use_guard_totp string in values/strings.xml and many locale-specific values-*/strings.xml files (da, de, es, fr, it, ko, pl, pt-rBR, ro, ru, uk, zh-rCN, zh-rTW). Minor whitespace/formatting fixes in some locale files (RU, ZH variants, PL).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as TwoFactor UI
    participant VM as UserLoginViewModel
    participant Auth as SteamAuthenticator
    participant Steam as SteamService

    User->>UI: tap "Use 2-factor auth code"
    UI->>VM: onUseGuardTotp()
    VM->>Auth: set useGuardTotp = true
    VM->>Steam: startLoginWithCredentials(authenticator=Auth)
    Steam->>Auth: request 2FA action (getDeviceCode / getEmailCode / acceptDeviceConfirmation)
    Auth->>VM: update loginState (DeviceAuth / EmailAuth / DeviceConfirm)
    VM->>UI: render two-factor prompt
    User->>UI: submit code
    UI->>Auth: send code via submitChannel
    Auth->>Steam: complete CompletableFuture with submitted code
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled bytes and hopped with glee,
New authenticator born for me,
Channels hum and states align,
Codes arrive in orderly line,
Hop—TOTP or guard—both pathways free.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature: adding Steam Guard TOTP code authentication support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is well-structured and complete, including all required sections with clear context, a recording, proper type selection, and all checklist items completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@peanut250 peanut250 changed the title Allow sign in with Steam Guard TOTP code feat: Allow sign in with Steam Guard TOTP code Apr 17, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/src/main/java/app/gamenative/service/SteamAuthenticator.kt (2)

15-19: Constructor properties should be val, not var.

loginState, submitChannel, and viewModelScope are never reassigned — making them var unnecessarily widens mutability. useGuardTotp rightly stays var since it’s toggled.

-class SteamAuthenticator(var loginState : MutableStateFlow<UserLoginState>,
-                         var submitChannel : Channel<String>,
-                         var viewModelScope : CoroutineScope) : IAuthenticator {
+class SteamAuthenticator(
+    private val loginState: MutableStateFlow<UserLoginState>,
+    private val submitChannel: Channel<String>,
+    private val viewModelScope: CoroutineScope,
+) : IAuthenticator {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/service/SteamAuthenticator.kt` around lines
15 - 19, The constructor of SteamAuthenticator declares mutable properties that
are never reassigned; change the constructor parameters loginState,
submitChannel, and viewModelScope from var to val to make them immutable,
leaving useGuardTotp as var since it is toggled; update the class header
signature (SteamAuthenticator(...)) to use val for those three identifiers so
their intent and thread-safety are clearer.

40-84: Tag inconsistency — log tag reads UserLoginViewModel inside SteamAuthenticator.

Now that this logic lives in its own class, the Timber tag should reflect that to avoid confusing log triage.

-        Timber.tag("UserLoginViewModel").d("Two-Factor, device code")
+        Timber.tag("SteamAuthenticator").d("Two-Factor, device code")

(Same for the other two overrides.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/service/SteamAuthenticator.kt` around lines
40 - 84, The Timber logs in SteamAuthenticator use the wrong tag
"UserLoginViewModel"; update the Timber.tag calls inside SteamAuthenticator
(notably in getDeviceCode and getEmailCode and the other two override methods
mentioned) to use a tag reflecting this class (e.g., "SteamAuthenticator" or
SteamAuthenticator::class.java.simpleName) so logs correctly identify the
source; locate the Timber.tag(...) usages in those override methods and replace
the string accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/service/SteamAuthenticator.kt`:
- Around line 21-38: The method acceptDeviceConfirmation in SteamAuthenticator
returns false when useGuardTotp is true but never clears that flag, causing
subsequent logins to always bypass device confirmation; modify
acceptDeviceConfirmation to consume and reset useGuardTotp (set it back to
false) before returning CompletableFuture.completedFuture(false) so the
authenticator can be reused on the next login attempt, keeping the existing
behavior for updating loginState
(loginResult/LoginScreen/isLoggingIn/lastTwoFactorMethod) otherwise unchanged.

In `@app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt`:
- Around line 262-275: The authenticator.useGuardTotp flag is being set in
useGuardTotp() and never reset, which permanently disables device-confirmation;
modify the credential-login flow to clear that flag before starting each new
SteamService.startLoginWithCredentials call (e.g., reset
authenticator.useGuardTotp = false at the start of the credential login path)
and also reset it in onLogonEnded whenever a terminal result (Success or Failed)
occurs so each new login starts with a clean authenticator state; locate
useGuardTotp(), the authenticator field, SteamService.startLoginWithCredentials
invocation, and onLogonEnded to apply these resets.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/service/SteamAuthenticator.kt`:
- Around line 15-19: The constructor of SteamAuthenticator declares mutable
properties that are never reassigned; change the constructor parameters
loginState, submitChannel, and viewModelScope from var to val to make them
immutable, leaving useGuardTotp as var since it is toggled; update the class
header signature (SteamAuthenticator(...)) to use val for those three
identifiers so their intent and thread-safety are clearer.
- Around line 40-84: The Timber logs in SteamAuthenticator use the wrong tag
"UserLoginViewModel"; update the Timber.tag calls inside SteamAuthenticator
(notably in getDeviceCode and getEmailCode and the other two override methods
mentioned) to use a tag reflecting this class (e.g., "SteamAuthenticator" or
SteamAuthenticator::class.java.simpleName) so logs correctly identify the
source; locate the Timber.tag(...) usages in those override methods and replace
the string accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3814d63d-eedf-41a0-9708-be8c404f4d2a

📥 Commits

Reviewing files that changed from the base of the PR and between a016ca4 and 6a8c875.

📒 Files selected for processing (5)
  • app/src/main/java/app/gamenative/service/SteamAuthenticator.kt
  • app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt
  • app/src/main/java/app/gamenative/ui/screen/login/TwoFactorAuthScreen.kt
  • app/src/main/java/app/gamenative/ui/screen/login/UserLoginScreen.kt
  • app/src/main/res/values/strings.xml

Comment thread app/src/main/java/app/gamenative/service/SteamAuthenticator.kt
Comment thread app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/service/SteamAuthenticator.kt">

<violation number="1" location="app/src/main/java/app/gamenative/service/SteamAuthenticator.kt:54">
P2: Returned CompletableFuture may never complete on channel closure/cancellation, causing authentication flow hangs.</violation>
</file>

<file name="app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt:263">
P2: `useGuardTotp` is set on a shared authenticator instance and never reset, so TOTP mode can leak into subsequent normal logins.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread app/src/main/java/app/gamenative/service/SteamAuthenticator.kt
Comment thread app/src/main/java/app/gamenative/ui/model/UserLoginViewModel.kt
@peanut250 peanut250 requested a review from utkarshdalal as a code owner April 17, 2026 21:59
Comment on lines -35 to -96
private val authenticator = object : IAuthenticator {
override fun acceptDeviceConfirmation(): CompletableFuture<Boolean> {
Timber.tag("UserLoginViewModel").i("Two-Factor, device confirmation")

_loginState.update { currentState ->
currentState.copy(
loginResult = LoginResult.DeviceConfirm,
loginScreen = LoginScreen.TWO_FACTOR,
isLoggingIn = false,
lastTwoFactorMethod = "steam_guard",
)
}

return CompletableFuture.completedFuture(true)
}

override fun getDeviceCode(previousCodeWasIncorrect: Boolean): CompletableFuture<String> {
Timber.tag("UserLoginViewModel").d("Two-Factor, device code")

_loginState.update { currentState ->
currentState.copy(
loginResult = LoginResult.DeviceAuth,
loginScreen = LoginScreen.TWO_FACTOR,
isLoggingIn = false,
previousCodeIncorrect = previousCodeWasIncorrect,
lastTwoFactorMethod = "authenticator_code",
)
}

return CompletableFuture<String>().apply {
viewModelScope.launch {
val code = submitChannel.receive()
complete(code)
}
}
}

override fun getEmailCode(
email: String?,
previousCodeWasIncorrect: Boolean,
): CompletableFuture<String> {
Timber.tag("UserLoginViewModel").d("Two-Factor, asking for email code")

_loginState.update { currentState ->
currentState.copy(
loginResult = LoginResult.EmailAuth,
loginScreen = LoginScreen.TWO_FACTOR,
isLoggingIn = false,
email = email,
previousCodeIncorrect = previousCodeWasIncorrect,
lastTwoFactorMethod = "email_code",
)
}

return CompletableFuture<String>().apply {
viewModelScope.launch {
val code = submitChannel.receive()
complete(code)
}
}
}
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason to pull this out into a file?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just organizational taste/readability since it is slightly expanded to allow for the TOTP code method.

<string name="steam_2fa_confirmation">Use the Steam Mobile App to confirm your sign in…</string>
<string name="steam_2fa_device">Please enter your 2-factor auth code from your authenticator app.</string>
<string name="steam_2fa_email">Please enter the auth code sent to the email at %s</string>
<string name="steam_2fa_use_guard_totp">Or use 2-factor auth code</string>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add translations for other languages also

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I can add machine translations as a best effort but have you considered something like Weblate for crowdsourced translations?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI translations are fine

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/res/values-da/strings.xml`:
- Line 1109: Update the Danish string resource steam_2fa_use_guard_totp so its
value uses the same 2FA terminology as the rest of the file; replace
"tofaktorautentificering" with the established term family (e.g.,
"tofaktorgodkendelse" or "2-faktor-godkendelseskode") to ensure UI consistency
for that resource.

In `@app/src/main/res/values-de/strings.xml`:
- Line 15: The string resource steam_2fa_use_guard_totp uses the formal
"verwenden Sie" which is inconsistent with the other Steam 2FA strings that use
informal "du" forms; update the value to the informal phrasing (e.g., replace
"Oder verwenden Sie den Code für die Zwei-Faktor-Authentifizierung" with an
informal imperative like "Oder verwende den Code für die
Zwei-Faktor-Authentifizierung") so the formality matches the other strings in
this section.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a14ec348-a66b-4439-975c-59ebf4d3a3c6

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6217c and f190827.

📒 Files selected for processing (14)
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (4)
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values/strings.xml

Comment thread app/src/main/res/values-da/strings.xml Outdated
Comment thread app/src/main/res/values-de/strings.xml Outdated
…ons from code review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@peanut250 peanut250 requested a review from utkarshdalal May 12, 2026 19:15
@utkarshdalal
Copy link
Copy Markdown
Owner

Just tried it out, good change but there are a few changes needed:

  1. the log in button on the TOTP page needs to be the same as the other buttons - purple background, white text
  2. timeout is occurring very quickly when entering the 2fa code. useGuardTotp() launches a new startLoginWithCredentials without cancelling the previous one. Both poll in parallel; when the first eventually times out it throws AsyncJobFailedException → LogonEnded(Failed) → clobbers UI state mid-flow. And then the user is stuck with a grey box:
    Uploading image.png…
    Need to handle timeouts and retry accordingly, like we do for QR code
  3. The new screen needs to be navigable by controller, and users should be able to submit the code with the enter button.

@utkarshdalal
Copy link
Copy Markdown
Owner

utkarshdalal commented May 17, 2026

Oh also, please remove the new SteamAuthenticator.kt file and put the code back where it was - this way it's easier to see if anything was actually diffed in that function or it's just a pure extraction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants