Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions drivers/123_open/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func (d *Open123) Init(ctx context.Context) error {
d.UploadThread = 3
}

if d.RefreshToken != "" {
// refresh token 直接主动刷新
if (d.UseOnlineAPI && d.RefreshToken != "" && len(d.APIAddress) > 0) || (d.ClientID != "" && d.ClientSecret != "") {
// proactive refresh by renewapi or client credentials
d.AccessToken = ""
d.tm = &tokenManager{}
} else {
Expand Down
10 changes: 7 additions & 3 deletions drivers/123_open/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import (
)

type Addition struct {
// refresh_token方式的AccessToken 【对个人开发者暂未开放】
RefreshToken string `json:"RefreshToken" required:"false"`

// 通过 https://www.123pan.com/developer 申请
ClientID string `json:"ClientID" required:"false"`
ClientSecret string `json:"ClientSecret" required:"false"`

// 直接写入AccessToken, AccessToken有过期时间,不建议直接填写
AccessToken string `json:"AccessToken" required:"false"`

// refresh_token方式的AccessToken 【对个人开发者暂未开放】
RefreshToken string `json:"RefreshToken" required:"false"`

// 使用在线API
UseOnlineAPI bool `json:"use_online_api" default:"true"`
APIAddress string `json:"api_url_address" default:"https://api.oplist.org/123cloud/renewapi"`

// 用户名+密码方式登录的AccessToken可以兼容
//Username string `json:"username" required:"false"`
//Password string `json:"password" required:"false"`
Expand Down
140 changes: 77 additions & 63 deletions drivers/123_open/token.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package _123_open

import (
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -13,10 +12,16 @@ import (
)

var (
AccessToken = "https://open-api.123pan.com/api/v1/access_token"
RefreshToken = "https://open-api.123pan.com/api/v1/oauth2/access_token"
AccessToken = "https://open-api.123pan.com/api/v1/access_token"
)

func expiresInToExpiredAt(expiresIn int64) (time.Time, error) {
if expiresIn <= 0 {
return time.Time{}, errors.New("invalid expires_in from official API")
}
return time.Now().UTC().Add(time.Duration(expiresIn) * time.Second), nil
}

type tokenManager struct {
// accessToken string
expiredAt time.Time
Expand All @@ -43,73 +48,82 @@ func (d *Open123) getAccessToken(forceRefresh bool) (string, error) {
}

func (d *Open123) flushAccessToken() error {
// directly send request to avoid deadlock
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"authorization": "Bearer " + d.AccessToken,
"platform": "open_platform",
"Content-Type": "application/json",
})
// Official app renewapi response contains access_token, refresh_token and expires_in.
if d.UseOnlineAPI && d.RefreshToken != "" && len(d.APIAddress) > 0 {
var resp RefreshTokenResp
_, err := base.RestyClient.R().
SetResult(&resp).
SetQueryParams(map[string]string{
"refresh_ui": d.RefreshToken,
"server_use": "true",
"driver_txt": "123cloud_oa",
}).
Get(d.APIAddress)
if err != nil {
return err
}

if d.ClientID != "" {
if d.RefreshToken != "" {
var resp RefreshTokenResp
req.SetQueryParam("client_id", d.ClientID)
if d.ClientSecret != "" {
req.SetQueryParam("client_secret", d.ClientSecret)
if resp.AccessToken == "" || resp.RefreshToken == "" {
errMessage := resp.ErrorDescription
if errMessage == "" {
errMessage = resp.Text
}
req.SetQueryParam("grant_type", "refresh_token")
req.SetQueryParam("refresh_token", d.RefreshToken)
req.SetResult(&resp)
res, err := req.Execute(http.MethodPost, RefreshToken)
if err != nil {
return err
if errMessage == "" {
errMessage = resp.Message
}
body := res.Body()
var baseResp BaseResp
if err = json.Unmarshal(body, &baseResp); err != nil {
return err
if errMessage == "" {
errMessage = resp.Error
}
if baseResp.Code != 0 {
return fmt.Errorf("get access token failed: %s", baseResp.Message)
if errMessage != "" {
return fmt.Errorf("failed to refresh token: %s", errMessage)
}
return fmt.Errorf("empty access_token or refresh_token returned from official API")
}
expiredAt, err := expiresInToExpiredAt(resp.ExpiresIn)
if err != nil {
return err
}

d.AccessToken = resp.AccessToken
// add token expire time
d.tm.expiredAt = time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second)
d.RefreshToken = resp.RefreshToken
op.MustSaveDriverStorage(d)
d.tm.blockRefresh = false
return nil
} else if d.ClientSecret != "" {
var resp AccessTokenResp
req.SetBody(base.Json{
"clientID": d.ClientID,
"clientSecret": d.ClientSecret,
})
req.SetResult(&resp)
res, err := req.Execute(http.MethodPost, AccessToken)
if err != nil {
return err
}
body := res.Body()
var baseResp BaseResp
if err = json.Unmarshal(body, &baseResp); err != nil {
return err
}
if baseResp.Code != 0 {
return fmt.Errorf("get access token failed: %s", baseResp.Message)
}
d.AccessToken = resp.Data.AccessToken
// parse token expire time
d.tm.expiredAt, err = time.Parse(time.RFC3339, resp.Data.ExpiredAt)
if err != nil {
return fmt.Errorf("parse expire time failed: %w", err)
}
op.MustSaveDriverStorage(d)
d.tm.blockRefresh = false
return nil
d.AccessToken = resp.AccessToken
d.RefreshToken = resp.RefreshToken
d.tm.expiredAt = expiredAt
op.MustSaveDriverStorage(d)
d.tm.blockRefresh = false
return nil
}

// Developer API response contains code/message/data(accessToken, expiredAt).
if d.ClientID != "" && d.ClientSecret != "" {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"platform": "open_platform",
"Content-Type": "application/json",
})
var resp AccessTokenResp
req.SetBody(base.Json{
"clientID": d.ClientID,
"clientSecret": d.ClientSecret,
})
req.SetResult(&resp)
_, err := req.Execute(http.MethodPost, AccessToken)
if err != nil {
return err
}
if resp.Code != 0 {
return fmt.Errorf("get access token failed: %s", resp.Message)
}
if resp.Data.AccessToken == "" || resp.Data.ExpiredAt == "" {
return errors.New("invalid token payload from developer API")
}
expiredAt, err := time.Parse(time.RFC3339, resp.Data.ExpiredAt)
if err != nil {
return fmt.Errorf("parse expire time failed: %w", err)
}
d.AccessToken = resp.Data.AccessToken
d.tm.expiredAt = expiredAt.UTC()
op.MustSaveDriverStorage(d)
d.tm.blockRefresh = false
return nil
}
return errors.New("no valid authentication method available")
}
13 changes: 8 additions & 5 deletions drivers/123_open/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,14 @@ type AccessTokenResp struct {
}

type RefreshTokenResp struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
Code int `json:"code"`
Message string `json:"message"`
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
Text string `json:"text"`
}

type UserInfoResp struct {
Expand Down
Loading