Skip to content

Commit 661e5c4

Browse files
committed
display QR codes for device flow authentication
use package rsc.io/qr to generate QR codes and display them in the terminal using ANSI escape codes.
1 parent 850067c commit 661e5c4

File tree

4 files changed

+55
-1
lines changed

4 files changed

+55
-1
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ go 1.23.0
55
toolchain go1.24.1
66

77
require golang.org/x/oauth2 v0.30.0
8+
9+
require rsc.io/qr v0.2.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
22
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
3+
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
4+
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=

main.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package main
1515

1616
import (
17+
"bytes"
1718
"context"
1819
"flag"
1920
"fmt"
@@ -31,6 +32,7 @@ import (
3132

3233
"golang.org/x/oauth2"
3334
"golang.org/x/oauth2/endpoints"
35+
"rsc.io/qr"
3436
)
3537

3638
// configByHost lists default config for several public hosts.
@@ -499,10 +501,47 @@ func getDeviceToken(ctx context.Context, c oauth2.Config) (*oauth2.Token, error)
499501
if verbose {
500502
fmt.Fprintf(os.Stderr, "%+v\n", deviceAuth)
501503
}
502-
fmt.Fprintf(os.Stderr, "Please enter code %s at %s\n", deviceAuth.UserCode, deviceAuth.VerificationURI)
504+
if deviceAuth.VerificationURIComplete != "" {
505+
fmt.Fprintf(os.Stderr, "Please scan the QR code or enter code %s at %s\n", deviceAuth.UserCode, deviceAuth.VerificationURI)
506+
writeQRCode(os.Stderr, deviceAuth.VerificationURIComplete)
507+
} else {
508+
fmt.Fprintf(os.Stderr, "Please enter code %s at %s\n", deviceAuth.UserCode, deviceAuth.VerificationURI)
509+
}
503510
return c.DeviceAccessToken(ctx, deviceAuth)
504511
}
505512

513+
func writeQRCode(w io.Writer, data string) error {
514+
// use low redundancy to generate small QR codes for terminal output.
515+
// we assume the user is sitting in front of their screen so image quality shouldn't be an issue
516+
code, err := qr.Encode(data, qr.L)
517+
if err != nil {
518+
return err
519+
}
520+
521+
var (
522+
black = "\033[40m \033[0m"
523+
white = "\033[107m \033[0m"
524+
whiteLine = strings.Repeat(white, code.Size+2) + "\n"
525+
buf bytes.Buffer
526+
)
527+
buf.WriteString(whiteLine)
528+
for y := range code.Size {
529+
for x := range code.Size {
530+
if code.Black(x, y) {
531+
buf.WriteString(black)
532+
} else {
533+
buf.WriteString(white)
534+
}
535+
}
536+
buf.WriteString(white)
537+
buf.WriteRune('\n')
538+
}
539+
buf.WriteString(whiteLine)
540+
541+
_, err = buf.WriteTo(w)
542+
return err
543+
}
544+
506545
func replaceHost(e oauth2.Endpoint, host string) oauth2.Endpoint {
507546
e.AuthURL = replaceHostInURL(e.AuthURL, host)
508547
e.TokenURL = replaceHostInURL(e.TokenURL, host)

main_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"os"
45
"strings"
56
"testing"
67
)
@@ -22,6 +23,16 @@ func TestConfig(t *testing.T) {
2223
}
2324
}
2425

26+
func TestQR(t *testing.T) {
27+
msg := os.Getenv("QR_MSG")
28+
if msg == "" {
29+
t.Skip("no QR_MSG set, skipping")
30+
}
31+
if err := writeQRCode(os.Stdout, msg); err != nil {
32+
t.Fatal(err)
33+
}
34+
}
35+
2536
func FuzzParse(f *testing.F) {
2637
f.Add("key=value")
2738
f.Add("key=")

0 commit comments

Comments
 (0)