Skip to content

[client] Display QR code for device auth login URL#5415

Open
typhoon1217 wants to merge 4 commits intonetbirdio:mainfrom
typhoon1217:feat/client-qr-device-auth
Open

[client] Display QR code for device auth login URL#5415
typhoon1217 wants to merge 4 commits intonetbirdio:mainfrom
typhoon1217:feat/client-qr-device-auth

Conversation

@typhoon1217
Copy link

@typhoon1217 typhoon1217 commented Feb 23, 2026

When running netbird up or netbird login on a headless machine without browser access, users must manually type the SSO device auth URL into a browser. This adds a --qr flag to both commands that prints a scannable QR code alongside the URL, allowing users to simply scan with their phone instead.

Describe your changes

  • Added printQRCode(writer io.Writer, url string) helper in client/cmd/qr.go that renders a QR code to the terminal using qrterminal.GenerateWithConfig
  • QR output is suppressed when the writer is not a TTY (e.g. piped output), so it doesn't pollute logs or scripts
  • Added --qr persistent flag to the up and login commands; QR display is opt-in and off by default
  • Updated openURL to accept a showQR bool parameter and call printQRCode when the flag is set
  • Added unit tests covering: empty URL (no output), non-TTY writer (no output), and valid URL (output produced)

Issue ticket number and link

N/A — this is a UX improvement for a pain point on headless/server setups where users have no browser access during SSO device auth flow.

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

CLI-only UX improvement, no config, API, or protocol changes. The --qr flag is self-explanatory and surfaces via netbird up --help / netbird login --help.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

When running `netbird up` or `netbird login` on a headless machine,
users must manually type the SSO URL into a browser. This adds a
scannable QR code alongside the URL for convenience.

The QR code is only displayed when stdout is a terminal, so piped
or redirected output remains clean. Uses half-block rendering for
a compact display.
@CLAassistant
Copy link

CLAassistant commented Feb 23, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds terminal QR code rendering to the SSO login flow: new unexported printQRCode prints a QR for non-empty URLs, openURL signature updated and wired to call it, a --show-qr flag is added to the up command, tests for QR printing were added, and QR dependencies were added to go.mod. (40 words)

Changes

Cohort / File(s) Summary
QR helper
client/cmd/qr.go
New unexported printQRCode(writer io.Writer, url string) that returns early on empty URL and generates a QR via qrterminal.GenerateWithConfig to the provided writer.
Login flow
client/cmd/login.go
openURL signature changed to add showQR bool; calls to openURL updated and printQRCode is invoked (QR printed regardless of browser usage when showQR true).
Command flag
client/cmd/up.go
Added showQRFlag constant, showQR internal variable, and registered a persistent --show-qr flag for the up command.
Tests
client/cmd/qr_test.go
Added tests verifying printQRCode produces no output for empty URL and writes output for a non-empty URL.
Dependencies
go.mod
Added indirect module requirements: github.com/mdp/qrterminal/v3 v3.2.1 and rsc.io/qr v0.2.0 to support QR generation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • mlsmaycon
  • heisbrot

Poem

🐰 I nibble bytes and draw a square,
A hop, a code, a tiny flare.
Scan the carrot, open the gate,
Tokens dance — the login's great!
Hop on, hop quick, rejoice and celebrate!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding QR code display for device auth login URLs in the client.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all major template sections with clear explanations of changes and justifications.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 1

🧹 Nitpick comments (2)
go.mod (1)

229-229: qrterminal/v3 is a direct dependency, not indirect.

github.com/mdp/qrterminal/v3 is directly imported by client/cmd/qr.go and client/cmd/qr_test.go, so the // indirect annotation and placement in the indirect-only require block are wrong. Running go mod tidy will move it to the direct-dependency block and drop the annotation.

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

In `@go.mod` at line 229, The go.mod lists github.com/mdp/qrterminal/v3 as
indirect but it is directly imported by client/cmd/qr.go and
client/cmd/qr_test.go; fix by removing the "// indirect" annotation and ensuring
the module appears in the direct require block (best done by running `go mod
tidy` or manually moving the require entry for github.com/mdp/qrterminal/v3 to
the main require section), so the dependency is correctly recorded as direct.
client/cmd/qr_test.go (1)

31-40: TestPrintQRCode_NonTerminalFile has no assertion.

The comment at line 38 says "no QR should be generated," but the test only verifies absence of a panic — it cannot confirm suppression since there's no output buffer. This is acceptable as a crash-guard, but a brief inline comment explaining the limitation (and that the non-terminal path is already validated by TestPrintQRCode_NonTerminalWriter) would make the intent clearer.

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

In `@client/cmd/qr_test.go` around lines 31 - 40, TestPrintQRCode_NonTerminalFile
lacks an assertion to confirm suppression; update the test by adding a brief
inline comment inside TestPrintQRCode_NonTerminalFile explaining that /dev/null
is used only to ensure no panic (we cannot capture output from a non-terminal
*os.File here) and that the non-terminal behavior is validated by the existing
TestPrintQRCode_NonTerminalWriter test; reference the function names
printQRCode, TestPrintQRCode_NonTerminalFile, and
TestPrintQRCode_NonTerminalWriter in the comment so future readers understand
why no assertion is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/cmd/qr.go`:
- Around line 24-33: The QuietZone value in the qrterminal.GenerateWithConfig
call is set to 1 which violates the QR spec; update the qrterminal.Config passed
to qrterminal.GenerateWithConfig so QuietZone uses the library default constant
(qrterminal.QUIET_ZONE) or at least 4 instead of 1; change the QuietZone field
in the qrterminal.Config literal used in this call to reference
qrterminal.QUIET_ZONE (or the numeric value 4) to ensure the required 4-module
margin.

---

Nitpick comments:
In `@client/cmd/qr_test.go`:
- Around line 31-40: TestPrintQRCode_NonTerminalFile lacks an assertion to
confirm suppression; update the test by adding a brief inline comment inside
TestPrintQRCode_NonTerminalFile explaining that /dev/null is used only to ensure
no panic (we cannot capture output from a non-terminal *os.File here) and that
the non-terminal behavior is validated by the existing
TestPrintQRCode_NonTerminalWriter test; reference the function names
printQRCode, TestPrintQRCode_NonTerminalFile, and
TestPrintQRCode_NonTerminalWriter in the comment so future readers understand
why no assertion is present.

In `@go.mod`:
- Line 229: The go.mod lists github.com/mdp/qrterminal/v3 as indirect but it is
directly imported by client/cmd/qr.go and client/cmd/qr_test.go; fix by removing
the "// indirect" annotation and ensuring the module appears in the direct
require block (best done by running `go mod tidy` or manually moving the require
entry for github.com/mdp/qrterminal/v3 to the main require section), so the
dependency is correctly recorded as direct.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44ef1a1 and 8edc1ea.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • client/cmd/login.go
  • client/cmd/qr.go
  • client/cmd/qr_test.go
  • go.mod

@typhoon1217 typhoon1217 marked this pull request as draft February 23, 2026 09:29
Copy link
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.

♻️ Duplicate comments (2)
client/cmd/qr.go (2)

32-32: QuietZone: qrterminal.QUIET_ZONE — past issue resolved.

The previous QuietZone: 1 violation of ISO/IEC 18004 has been corrected to use the library-provided default.

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

In `@client/cmd/qr.go` at line 32, QuietZone was previously hard-coded to 1
(violating ISO/IEC 18004); update the QR config to use the library default by
setting QuietZone to qrterminal.QUIET_ZONE in the QR generation configuration
(locate the struct or options creation where QuietZone is set in
client/cmd/qr.go, replace any literal 1 with qrterminal.QUIET_ZONE).

32-32: QuietZone: qrterminal.QUIET_ZONE — past issue resolved.

The prior QuietZone: 1 has been corrected to use the library-provided default constant.

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

In `@client/cmd/qr.go` at line 32, Summary: Replace hardcoded QuietZone value with
library constant and ensure consistency. Locate uses of the QuietZone field
(e.g., the struct literal that currently sets QuietZone) and replace any numeric
literal (like 1) with qrterminal.QUIET_ZONE, ensure the qrterminal package is
imported where this struct is constructed, and audit the repo for any other
occurrences of a hardcoded QuietZone and update them to use
qrterminal.QUIET_ZONE for consistency.
🧹 Nitpick comments (2)
client/cmd/qr.go (2)

25-25: Consider upgrading error correction to Level M for better real-world scan reliability.

qrterminal.L (7% error correction) is the lowest tier. OAuth verificationURIComplete URLs are typically scanned from a screen — terminal font rendering, sub-pixel anti-aliasing, and screen glare can introduce pixel-level noise that Level L cannot recover from. qrterminal.M (15%) is the standard choice for URL QR codes and adds negligible size overhead.

♻️ Proposed change
-		Level:      qrterminal.L,
+		Level:      qrterminal.M,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/cmd/qr.go` at line 25, The QR generation currently sets the error
correction Level to qrterminal.L; update it to qrterminal.M to increase error
correction from 7% to 15% for more reliable real-world scanning. Locate the QR
options where Level: qrterminal.L is set (in the QR generation function/variable
that constructs the qrterminal config) and change it to Level: qrterminal.M; no
other changes required.

25-25: Consider Level M over Level L for better real-world scan reliability.

qrterminal.L provides only 7% error correction. OAuth verificationURIComplete URLs are typically scanned from a screen where terminal font rendering, sub-pixel anti-aliasing, and glare introduce pixel-level noise that Level L cannot recover from. The library's own README examples use Level: qrterminal.M as the standard choice. Level M (15%) adds negligible size overhead for this use case.

♻️ Proposed change
-		Level:      qrterminal.L,
+		Level:      qrterminal.M,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/cmd/qr.go` at line 25, Update the QR terminal configuration to use
higher error correction: change the QR config that sets "Level: qrterminal.L" to
"Level: qrterminal.M" so generated OTP/verificationURIComplete QR codes use 15%
EC; locate the QR generation code where the qrterminal config is constructed
(the struct or map containing the Level field) and replace qrterminal.L with
qrterminal.M to improve scan reliability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@client/cmd/qr.go`:
- Line 32: QuietZone was previously hard-coded to 1 (violating ISO/IEC 18004);
update the QR config to use the library default by setting QuietZone to
qrterminal.QUIET_ZONE in the QR generation configuration (locate the struct or
options creation where QuietZone is set in client/cmd/qr.go, replace any literal
1 with qrterminal.QUIET_ZONE).
- Line 32: Summary: Replace hardcoded QuietZone value with library constant and
ensure consistency. Locate uses of the QuietZone field (e.g., the struct literal
that currently sets QuietZone) and replace any numeric literal (like 1) with
qrterminal.QUIET_ZONE, ensure the qrterminal package is imported where this
struct is constructed, and audit the repo for any other occurrences of a
hardcoded QuietZone and update them to use qrterminal.QUIET_ZONE for
consistency.

---

Nitpick comments:
In `@client/cmd/qr.go`:
- Line 25: The QR generation currently sets the error correction Level to
qrterminal.L; update it to qrterminal.M to increase error correction from 7% to
15% for more reliable real-world scanning. Locate the QR options where Level:
qrterminal.L is set (in the QR generation function/variable that constructs the
qrterminal config) and change it to Level: qrterminal.M; no other changes
required.
- Line 25: Update the QR terminal configuration to use higher error correction:
change the QR config that sets "Level: qrterminal.L" to "Level: qrterminal.M" so
generated OTP/verificationURIComplete QR codes use 15% EC; locate the QR
generation code where the qrterminal config is constructed (the struct or map
containing the Level field) and replace qrterminal.L with qrterminal.M to
improve scan reliability.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8edc1ea and 34a6d25.

📒 Files selected for processing (1)
  • client/cmd/qr.go

@typhoon1217 typhoon1217 force-pushed the feat/client-qr-device-auth branch from fdb58d1 to 0193d7c Compare February 24, 2026 01:50
- Upgrade error correction Level L to M for better scan reliability
- Replace hardcoded QuietZone with qrterminal.QUIET_ZONE constant
- Clarify test comment for non-terminal file guard
Remove automatic TTY-based QR display. Users on headless machines without
browser access can now explicitly request a QR code via --qr on both
netbird login and netbird up commands.
@typhoon1217 typhoon1217 force-pushed the feat/client-qr-device-auth branch from 0193d7c to cc70276 Compare February 24, 2026 02:07
@sonarqubecloud
Copy link

@typhoon1217 typhoon1217 marked this pull request as ready for review February 24, 2026 02:09
@typhoon1217
Copy link
Author

@mlsmaycon @heisbrot — would appreciate your review when you have a moment! This adds an optional --qr flag for headless/server environments where users can't open a browser during SSO device auth. It's opt-in, TTY-gated, and has no impact on existing flows.

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