fix: harden custom QR login output

This commit is contained in:
2026-07-20 11:20:39 +08:00
parent 88df3149be
commit 74486ebf08
3 changed files with 66 additions and 13 deletions
+36 -12
View File
@@ -41,24 +41,48 @@ func main() {
`auth.json` 包含 `serviceToken``passToken``ssecurity` 等敏感认证数据。库写入该文件时使用 `0600` 权限;请保持此权限,并且不要将该文件提交到版本库。 `auth.json` 包含 `serviceToken``passToken``ssecurity` 等敏感认证数据。库写入该文件时使用 `0600` 权限;请保持此权限,并且不要将该文件提交到版本库。
Web/GUI 应用可通过 `WithQRWriter` 接收登录输出,再将其展示给用户: Web/GUI 应用可通过 `WithQRWriter` 接收登录输出,并在阻塞的 `Login` 等待期间实时展示给用户:
```go ```go
var loginOutput bytes.Buffer import (
client, err := mijia.NewClient("", mijia.WithQRWriter(&loginOutput)) "bufio"
if err != nil { "context"
return err "io"
}
if _, err := client.Login(ctx); err != nil { mijia "git.misaka.ren/m1saka/mijia-go-api"
return err )
}
// loginOutput 包含二维码 URL 和终端二维码文本,可交给 Web/GUI 展示层。 func streamLogin(ctx context.Context, renderLoginLine func(string)) error {
renderLoginOutput(loginOutput.String()) reader, writer := io.Pipe()
defer reader.Close()
client, err := mijia.NewClient("", mijia.WithQRWriter(writer))
if err != nil {
writer.Close()
return err
}
loginDone := make(chan error, 1)
go func() {
_, err := client.Login(ctx)
writer.CloseWithError(err)
loginDone <- err
}()
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
renderLoginLine(scanner.Text())
}
loginErr := <-loginDone
if loginErr != nil {
return loginErr
}
return scanner.Err()
}
``` ```
Writer 接收的内容包含登录二维码 URL,属于敏感登录信息;调用方不得将其写入日志、监控事件或其他持久化记录。 `Login` 会阻塞等待扫码,因此必须同步消费 Writer 输出。Writer 可能由另一个 goroutine 写入,不能无同步地并发读写 `bytes.Buffer`Writer 接收的内容包含登录二维码 URL,属于敏感登录信息;调用方不得将其写入日志、监控事件或其他持久化记录。
## 底层 API ## 底层 API
+10 -1
View File
@@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -39,7 +40,15 @@ func WithHTTPClient(httpClient *http.Client) Option {
// WithQRWriter configures where QR login output is written. // WithQRWriter configures where QR login output is written.
func WithQRWriter(writer io.Writer) Option { func WithQRWriter(writer io.Writer) Option {
return func(client *Client) error { return func(client *Client) error {
if writer == nil { isNil := writer == nil
if !isNil {
value := reflect.ValueOf(writer)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
isNil = value.IsNil()
}
}
if isNil {
return fmt.Errorf("QR writer must not be nil") return fmt.Errorf("QR writer must not be nil")
} }
client.qrWriter = writer client.qrWriter = writer
+20
View File
@@ -37,6 +37,26 @@ func TestWithQRWriterRejectsNil(t *testing.T) {
} }
} }
func TestWithQRWriterRejectsTypedNil(t *testing.T) {
var output *bytes.Buffer
_, err := NewClient(t.TempDir(), WithQRWriter(output))
if err == nil || err.Error() != "QR writer must not be nil" {
t.Fatalf("error = %v, want QR writer must not be nil", err)
}
}
func TestWithQRWriterAcceptsStructWriter(t *testing.T) {
if _, err := NewClient(t.TempDir(), WithQRWriter(structWriter{})); err != nil {
t.Fatal(err)
}
}
type structWriter struct{}
func (structWriter) Write(data []byte) (int, error) {
return len(data), nil
}
func TestDefaultQRWriterIsStdout(t *testing.T) { func TestDefaultQRWriterIsStdout(t *testing.T) {
client, err := NewClient(t.TempDir()) client, err := NewClient(t.TempDir())
if err != nil { if err != nil {