fix: harden custom QR login output
This commit is contained in:
@@ -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,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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user