feat: identify required reauthentication
This commit is contained in:
@@ -179,4 +179,12 @@ case errors.As(err, &deviceErr):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Token 刷新失败且必须重新扫码授权时,可使用 `errors.Is` 稳定判断,同时仍可通过 `errors.As` 获取 `LoginError` 详情:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if errors.Is(err, mijia.ErrReauthenticationRequired) {
|
||||||
|
log.Print("认证已失效,请重新扫码授权")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
写属性和执行 action 时,对应错误类型为 `DeviceSetError` 和 `DeviceActionError`;设备选择还可能返回 `DeviceNotFoundError` 或 `MultipleDevicesFoundError`。
|
写属性和执行 action 时,对应错误类型为 `DeviceSetError` 和 `DeviceActionError`;设备选择还可能返回 `DeviceNotFoundError` 或 `MultipleDevicesFoundError`。
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ func (client *Client) getLocation(ctx context.Context) (url.Values, bool, error)
|
|||||||
serviceTokenReceived := updateAuthDataFromCookies(&candidate, httpClient, response.Request.URL)
|
serviceTokenReceived := updateAuthDataFromCookies(&candidate, httpClient, response.Request.URL)
|
||||||
candidate.Ssecurity = data.Ssecurity
|
candidate.Ssecurity = data.Ssecurity
|
||||||
if !serviceTokenReceived || !candidate.complete() {
|
if !serviceTokenReceived || !candidate.complete() {
|
||||||
return nil, false, &LoginError{Code: -1, Message: "刷新Token响应认证信息不完整"}
|
return nil, false, fmt.Errorf("%w: %w", ErrReauthenticationRequired, &LoginError{Code: -1, Message: "刷新Token响应认证信息不完整"})
|
||||||
}
|
}
|
||||||
candidate.ExpireTime = time.Now().Add(30 * 24 * time.Hour).UnixMilli()
|
candidate.ExpireTime = time.Now().Add(30 * 24 * time.Hour).UnixMilli()
|
||||||
client.setAuthData(candidate)
|
client.setAuthData(candidate)
|
||||||
@@ -544,7 +544,7 @@ func (client *Client) refreshToken(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !refreshed {
|
if !refreshed {
|
||||||
return &LoginError{Code: -1, Message: "刷新Token失败,请重新登录"}
|
return fmt.Errorf("%w: %w", ErrReauthenticationRequired, &LoginError{Code: -1, Message: "刷新Token失败,请重新登录"})
|
||||||
}
|
}
|
||||||
if err := client.saveAuthData(); err != nil {
|
if err := client.saveAuthData(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ func TestRefreshRejectsMissingNewServiceTokenWithoutSaving(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = client.refreshToken(context.Background())
|
err = client.refreshToken(context.Background())
|
||||||
|
if !errors.Is(err, ErrReauthenticationRequired) {
|
||||||
|
t.Fatalf("refreshToken() error = %v, want ErrReauthenticationRequired", err)
|
||||||
|
}
|
||||||
var loginErr *LoginError
|
var loginErr *LoginError
|
||||||
if !errors.As(err, &loginErr) {
|
if !errors.As(err, &loginErr) {
|
||||||
t.Fatalf("refreshToken() error = %v, want LoginError", err)
|
t.Fatalf("refreshToken() error = %v, want LoginError", err)
|
||||||
@@ -153,6 +156,56 @@ func TestRefreshRejectsMissingNewServiceTokenWithoutSaving(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRefreshWithoutNewTokenRequiresReauthentication(t *testing.T) {
|
||||||
|
var server *httptest.Server
|
||||||
|
server = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
switch request.URL.Path {
|
||||||
|
case "/v2/message/v2/check_new_msg":
|
||||||
|
_, _ = io.WriteString(writer, `{"code":-10030,"message":"expired"}`)
|
||||||
|
case "/serviceLogin":
|
||||||
|
_, _ = io.WriteString(writer, `&&&START&&&{"code":70016,"location":"`+server.URL+`/qr"}`)
|
||||||
|
default:
|
||||||
|
http.NotFound(writer, request)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := testClient(t, server.Client())
|
||||||
|
client.baseURL = server.URL
|
||||||
|
client.serviceLoginURL = server.URL + "/serviceLogin"
|
||||||
|
client.availabilityValid = false
|
||||||
|
|
||||||
|
err := client.refreshToken(context.Background())
|
||||||
|
if !errors.Is(err, ErrReauthenticationRequired) {
|
||||||
|
t.Fatalf("refreshToken() error = %v, want ErrReauthenticationRequired", err)
|
||||||
|
}
|
||||||
|
var loginErr *LoginError
|
||||||
|
if !errors.As(err, &loginErr) {
|
||||||
|
t.Fatalf("refreshToken() error = %v, want LoginError", err)
|
||||||
|
}
|
||||||
|
if loginErr.Code != -1 || loginErr.Message != "刷新Token失败,请重新登录" {
|
||||||
|
t.Fatalf("LoginError = %#v", loginErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQRLoginTimeoutDoesNotRequireReauthentication(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client := testClient(t, http.DefaultClient)
|
||||||
|
_, err := client.completeQRLogin(ctx, qrLoginData{LP: "https://example.invalid/long-poll"})
|
||||||
|
var loginErr *LoginError
|
||||||
|
if !errors.As(err, &loginErr) {
|
||||||
|
t.Fatalf("completeQRLogin() error = %v, want LoginError", err)
|
||||||
|
}
|
||||||
|
if loginErr.Code != -1 {
|
||||||
|
t.Fatalf("LoginError.Code = %d, want -1", loginErr.Code)
|
||||||
|
}
|
||||||
|
if errors.Is(err, ErrReauthenticationRequired) {
|
||||||
|
t.Fatalf("completeQRLogin() error = %v, do not want ErrReauthenticationRequired", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthDataReturnsDeepCopy(t *testing.T) {
|
func TestAuthDataReturnsDeepCopy(t *testing.T) {
|
||||||
client := testClient(t, http.DefaultClient)
|
client := testClient(t, http.DefaultClient)
|
||||||
client.updateAuthData(func(authData *AuthData) { authData.Extra = map[string]string{"cookie": "original"} })
|
client.updateAuthData(func(authData *AuthData) { authData.Extra = map[string]string{"cookie": "original"} })
|
||||||
|
|||||||
Reference in New Issue
Block a user