mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-20 12:46:17 +08:00
Compare commits
1 Commits
v4.1.2
...
renovate/g
Author | SHA1 | Date | |
---|---|---|---|
7350b44036 |
@ -17,7 +17,6 @@ import (
|
||||
type Open123 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
UID uint64
|
||||
}
|
||||
|
||||
func (d *Open123) Config() driver.Config {
|
||||
@ -84,7 +83,7 @@ func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
||||
}, nil
|
||||
}
|
||||
|
||||
uid, err := d.getUID()
|
||||
u, err := d.getUserInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -92,7 +91,7 @@ func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
||||
duration := time.Duration(d.DirectLinkValidDuration) * time.Minute
|
||||
|
||||
newURL, err := d.SignURL(res.Data.URL, d.DirectLinkPrivateKey,
|
||||
uid, duration)
|
||||
u.Data.UID, duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -128,18 +128,18 @@ type UserInfoResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
UID uint64 `json:"uid"`
|
||||
// Username string `json:"username"`
|
||||
// DisplayName string `json:"displayName"`
|
||||
// HeadImage string `json:"headImage"`
|
||||
// Passport string `json:"passport"`
|
||||
// Mail string `json:"mail"`
|
||||
// SpaceUsed int64 `json:"spaceUsed"`
|
||||
// SpacePermanent int64 `json:"spacePermanent"`
|
||||
// SpaceTemp int64 `json:"spaceTemp"`
|
||||
// SpaceTempExpr int64 `json:"spaceTempExpr"`
|
||||
// Vip bool `json:"vip"`
|
||||
// DirectTraffic int64 `json:"directTraffic"`
|
||||
// IsHideUID bool `json:"isHideUID"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"displayName"`
|
||||
HeadImage string `json:"headImage"`
|
||||
Passport string `json:"passport"`
|
||||
Mail string `json:"mail"`
|
||||
SpaceUsed int64 `json:"spaceUsed"`
|
||||
SpacePermanent int64 `json:"spacePermanent"`
|
||||
SpaceTemp int64 `json:"spaceTemp"`
|
||||
SpaceTempExpr string `json:"spaceTempExpr"`
|
||||
Vip bool `json:"vip"`
|
||||
DirectTraffic int64 `json:"directTraffic"`
|
||||
IsHideUID bool `json:"isHideUID"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
|
@ -158,18 +158,6 @@ func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (d *Open123) getUID() (uint64, error) {
|
||||
if d.UID != 0 {
|
||||
return d.UID, nil
|
||||
}
|
||||
resp, err := d.getUserInfo()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.UID = resp.Data.UID
|
||||
return resp.Data.UID, nil
|
||||
}
|
||||
|
||||
func (d *Open123) getFiles(parentFileId int64, limit int, lastFileId int64) (*FileListResp, error) {
|
||||
var resp FileListResp
|
||||
|
||||
@ -212,7 +200,7 @@ func (d *Open123) getDirectLink(fileId int64) (*DirectLinkResp, error) {
|
||||
|
||||
_, err := d.Request(DirectLink, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"fileID": strconv.FormatInt(fileId, 10),
|
||||
"fileId": strconv.FormatInt(fileId, 10),
|
||||
})
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
|
@ -21,8 +21,6 @@ type CloudreveV4 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
ref *CloudreveV4
|
||||
AccessExpires string
|
||||
RefreshExpires string
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Config() driver.Config {
|
||||
@ -46,17 +44,13 @@ func (d *CloudreveV4) Init(ctx context.Context) error {
|
||||
if d.ref != nil {
|
||||
return nil
|
||||
}
|
||||
if d.canLogin() {
|
||||
return d.login()
|
||||
}
|
||||
if d.RefreshToken != "" {
|
||||
if d.AccessToken == "" && d.RefreshToken != "" {
|
||||
return d.refreshToken()
|
||||
}
|
||||
if d.AccessToken == "" {
|
||||
return errors.New("no way to authenticate. At least AccessToken is required")
|
||||
if d.Username != "" {
|
||||
return d.login()
|
||||
}
|
||||
// ensure AccessToken is valid
|
||||
return d.parseJWT(d.AccessToken, &AccessJWT{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) InitReference(storage driver.Driver) error {
|
||||
|
@ -66,27 +66,11 @@ type CaptchaResp struct {
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
type AccessJWT struct {
|
||||
TokenType string `json:"token_type"`
|
||||
Sub string `json:"sub"`
|
||||
Exp int64 `json:"exp"`
|
||||
Nbf int64 `json:"nbf"`
|
||||
}
|
||||
|
||||
type RefreshJWT struct {
|
||||
TokenType string `json:"token_type"`
|
||||
Sub string `json:"sub"`
|
||||
Exp int `json:"exp"`
|
||||
Nbf int `json:"nbf"`
|
||||
StateHash string `json:"state_hash"`
|
||||
RootTokenID string `json:"root_token_id"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessExpires string `json:"access_expires"`
|
||||
RefreshExpires string `json:"refresh_expires"`
|
||||
AccessExpires time.Time `json:"access_expires"`
|
||||
RefreshExpires time.Time `json:"refresh_expires"`
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
|
@ -28,15 +28,6 @@ import (
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
const (
|
||||
CodeLoginRequired = http.StatusUnauthorized
|
||||
CodeCredentialInvalid = 40020 // Failed to issue token
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorIssueToken = errors.New("failed to issue token")
|
||||
)
|
||||
|
||||
func (d *CloudreveV4) getUA() string {
|
||||
if d.CustomUA != "" {
|
||||
return d.CustomUA
|
||||
@ -48,23 +39,6 @@ func (d *CloudreveV4) request(method string, path string, callback base.ReqCallb
|
||||
if d.ref != nil {
|
||||
return d.ref.request(method, path, callback, out)
|
||||
}
|
||||
|
||||
// ensure token
|
||||
if d.isTokenExpired() {
|
||||
err := d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return d._request(method, path, callback, out)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) _request(method string, path string, callback base.ReqCallback, out any) error {
|
||||
if d.ref != nil {
|
||||
return d.ref._request(method, path, callback, out)
|
||||
}
|
||||
|
||||
u := d.Address + "/api/v4" + path
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
@ -91,17 +65,15 @@ func (d *CloudreveV4) _request(method string, path string, callback base.ReqCall
|
||||
}
|
||||
|
||||
if r.Code != 0 {
|
||||
if r.Code == CodeLoginRequired && d.canLogin() && path != "/session/token/refresh" {
|
||||
err = d.login()
|
||||
if r.Code == 401 && d.RefreshToken != "" && path != "/session/token/refresh" {
|
||||
// try to refresh token
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.request(method, path, callback, out)
|
||||
}
|
||||
if r.Code == CodeCredentialInvalid {
|
||||
return ErrorIssueToken
|
||||
}
|
||||
return fmt.Errorf("%d: %s", r.Code, r.Msg)
|
||||
return errors.New(r.Msg)
|
||||
}
|
||||
|
||||
if out != nil && r.Data != nil {
|
||||
@ -119,18 +91,14 @@ func (d *CloudreveV4) _request(method string, path string, callback base.ReqCall
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) canLogin() bool {
|
||||
return d.Username != "" && d.Password != ""
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) login() error {
|
||||
var siteConfig SiteLoginConfigResp
|
||||
err := d._request(http.MethodGet, "/site/config/login", nil, &siteConfig)
|
||||
err := d.request(http.MethodGet, "/site/config/login", nil, &siteConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var prepareLogin PrepareLoginResp
|
||||
err = d._request(http.MethodGet, "/session/prepare?email="+d.Addition.Username, nil, &prepareLogin)
|
||||
err = d.request(http.MethodGet, "/session/prepare?email="+d.Addition.Username, nil, &prepareLogin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -160,7 +128,7 @@ func (d *CloudreveV4) doLogin(needCaptcha bool) error {
|
||||
}
|
||||
if needCaptcha {
|
||||
var config BasicConfigResp
|
||||
err = d._request(http.MethodGet, "/site/config/basic", nil, &config)
|
||||
err = d.request(http.MethodGet, "/site/config/basic", nil, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -168,7 +136,7 @@ func (d *CloudreveV4) doLogin(needCaptcha bool) error {
|
||||
return fmt.Errorf("captcha type %s not support", config.CaptchaType)
|
||||
}
|
||||
var captcha CaptchaResp
|
||||
err = d._request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||
err = d.request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -194,22 +162,20 @@ func (d *CloudreveV4) doLogin(needCaptcha bool) error {
|
||||
loginBody["captcha"] = captchaCode
|
||||
}
|
||||
var token TokenResponse
|
||||
err = d._request(http.MethodPost, "/session/token", func(req *resty.Request) {
|
||||
err = d.request(http.MethodPost, "/session/token", func(req *resty.Request) {
|
||||
req.SetBody(loginBody)
|
||||
}, &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.AccessToken, d.RefreshToken = token.Token.AccessToken, token.Token.RefreshToken
|
||||
d.AccessExpires, d.RefreshExpires = token.Token.AccessExpires, token.Token.RefreshExpires
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) refreshToken() error {
|
||||
// if no refresh token, try to login if possible
|
||||
if d.RefreshToken == "" {
|
||||
if d.canLogin() {
|
||||
if d.Username != "" {
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot login to get refresh token, error: %s", err)
|
||||
@ -217,127 +183,20 @@ func (d *CloudreveV4) refreshToken() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse jwt to check if refresh token is valid
|
||||
var jwt RefreshJWT
|
||||
err := d.parseJWT(d.RefreshToken, &jwt)
|
||||
if err != nil {
|
||||
// if refresh token is invalid, try to login if possible
|
||||
if d.canLogin() {
|
||||
return d.login()
|
||||
}
|
||||
d.GetStorage().SetStatus(fmt.Sprintf("Invalid RefreshToken: %s", err.Error()))
|
||||
op.MustSaveDriverStorage(d)
|
||||
return fmt.Errorf("invalid refresh token: %w", err)
|
||||
}
|
||||
|
||||
// do refresh token
|
||||
var token Token
|
||||
err = d._request(http.MethodPost, "/session/token/refresh", func(req *resty.Request) {
|
||||
err := d.request(http.MethodPost, "/session/token/refresh", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"refresh_token": d.RefreshToken,
|
||||
})
|
||||
}, &token)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrorIssueToken) {
|
||||
if d.canLogin() {
|
||||
// try to login again
|
||||
return d.login()
|
||||
}
|
||||
d.GetStorage().SetStatus("This session is no longer valid")
|
||||
op.MustSaveDriverStorage(d)
|
||||
return ErrorIssueToken
|
||||
}
|
||||
return err
|
||||
}
|
||||
d.AccessToken, d.RefreshToken = token.AccessToken, token.RefreshToken
|
||||
d.AccessExpires, d.RefreshExpires = token.AccessExpires, token.RefreshExpires
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) parseJWT(token string, jwt any) error {
|
||||
split := strings.Split(token, ".")
|
||||
if len(split) != 3 {
|
||||
return fmt.Errorf("invalid token length: %d, ensure the token is a valid JWT", len(split))
|
||||
}
|
||||
data, err := base64.RawURLEncoding.DecodeString(split[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token encoding: %w, ensure the token is a valid JWT", err)
|
||||
}
|
||||
err = json.Unmarshal(data, &jwt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token content: %w, ensure the token is a valid JWT", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if token is expired
|
||||
// https://github.com/cloudreve/frontend/blob/ddfacc1c31c49be03beb71de4cc114c8811038d6/src/session/index.ts#L177-L200
|
||||
func (d *CloudreveV4) isTokenExpired() bool {
|
||||
if d.RefreshToken == "" {
|
||||
// login again if username and password is set
|
||||
if d.canLogin() {
|
||||
return true
|
||||
}
|
||||
// no refresh token, cannot refresh
|
||||
return false
|
||||
}
|
||||
if d.AccessToken == "" {
|
||||
return true
|
||||
}
|
||||
var (
|
||||
err error
|
||||
expires time.Time
|
||||
)
|
||||
// check if token is expired
|
||||
if d.AccessExpires != "" {
|
||||
// use expires field if possible to prevent timezone issue
|
||||
// only available after login or refresh token
|
||||
// 2025-08-28T02:43:07.645109985+08:00
|
||||
expires, err = time.Parse(time.RFC3339Nano, d.AccessExpires)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// fallback to parse jwt
|
||||
// if failed, disable the storage
|
||||
var jwt AccessJWT
|
||||
err = d.parseJWT(d.AccessToken, &jwt)
|
||||
if err != nil {
|
||||
d.GetStorage().SetStatus(fmt.Sprintf("Invalid AccessToken: %s", err.Error()))
|
||||
op.MustSaveDriverStorage(d)
|
||||
return false
|
||||
}
|
||||
// may be have timezone issue
|
||||
expires = time.Unix(jwt.Exp, 0)
|
||||
}
|
||||
// add a 10 minutes safe margin
|
||||
ddl := time.Now().Add(10 * time.Minute)
|
||||
if expires.Before(ddl) {
|
||||
// current access token expired, check if refresh token is expired
|
||||
// warning: cannot parse refresh token from jwt, because the exp field is not standard
|
||||
if d.RefreshExpires != "" {
|
||||
refreshExpires, err := time.Parse(time.RFC3339Nano, d.RefreshExpires)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if refreshExpires.Before(time.Now()) {
|
||||
// This session is no longer valid
|
||||
if d.canLogin() {
|
||||
// try to login again
|
||||
return true
|
||||
}
|
||||
d.GetStorage().SetStatus("This session is no longer valid")
|
||||
op.MustSaveDriverStorage(d)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) upLocal(ctx context.Context, file model.FileStreamer, u FileUploadResp, up driver.UpdateProgress) error {
|
||||
var finish int64 = 0
|
||||
var chunk int = 0
|
||||
|
6
go.mod
6
go.mod
@ -41,7 +41,7 @@ require (
|
||||
github.com/hirochachacha/go-smb2 v1.1.0
|
||||
github.com/ipfs/go-ipfs-api v0.7.0
|
||||
github.com/itsHenry35/gofakes3 v0.0.8
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20250831012827-3f092e051c94
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kdomanski/iso9660 v0.4.0
|
||||
github.com/maruel/natural v1.1.1
|
||||
@ -58,7 +58,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/afero v1.14.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20241213151442-a19cff0ec7b5
|
||||
github.com/u2takey/ffmpeg-go v0.5.0
|
||||
github.com/upyun/go-sdk/v3 v3.0.4
|
||||
@ -254,7 +254,7 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0
|
||||
|
4
go.sum
4
go.sum
@ -402,6 +402,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3 h1:ZxO6Qr2GOXPdcW80Mcn3nemvilMPvpWqxrNfK2ZnNNs=
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3/go.mod h1:dvLUr/8Fs9a2OBrEnCC5duphbkz/k/mSy5OkXg3PAgI=
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20250831012827-3f092e051c94 h1:sBUrMD4Gx91zDgzTqPCr3FqFs2+3wWX7lyUYIP/isuA=
|
||||
github.com/jlaffaye/ftp v0.2.1-0.20250831012827-3f092e051c94/go.mod h1:H1+whwD0Qe3YOunlXIWhh3rlvzW5cZfkMDYGQPg+KAM=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
@ -620,6 +622,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20241213151442-a19cff0ec7b5 h1:Sa+sR8aaAMFwxhXWENEnE6ZpqhZ9d7u1RT2722Rw6hc=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20241213151442-a19cff0ec7b5/go.mod h1:UdZiFUFu6e2WjjtjxivwXWcwc1N/8zgbkBR9QNucUOY=
|
||||
github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543 h1:6Y51mutOvRGRx6KqyMNo//xk8B8o6zW9/RVmy1VamOs=
|
||||
|
@ -486,18 +486,12 @@ func Rename(ctx context.Context, storage driver.Driver, srcPath, dstName string,
|
||||
updateCacheObj(storage, srcDirPath, srcRawObj, model.WrapObjName(newObj))
|
||||
} else if !utils.IsBool(lazyCache...) {
|
||||
DeleteCache(storage, srcDirPath)
|
||||
if srcRawObj.IsDir() {
|
||||
ClearCache(storage, srcPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
case driver.Rename:
|
||||
err = s.Rename(ctx, srcObj, dstName)
|
||||
if err == nil && !utils.IsBool(lazyCache...) {
|
||||
DeleteCache(storage, srcDirPath)
|
||||
if srcRawObj.IsDir() {
|
||||
ClearCache(storage, srcPath)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errs.NotImplement
|
||||
|
Reference in New Issue
Block a user