mirror of
https://github.com/rclone/rclone.git
synced 2026-02-07 22:02:17 -05:00
147 lines
4.0 KiB
Go
147 lines
4.0 KiB
Go
// Package internxt provides authentication handling
|
|
package internxt
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
internxtauth "github.com/internxt/rclone-adapter/auth"
|
|
internxtconfig "github.com/internxt/rclone-adapter/config"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
"github.com/rclone/rclone/lib/oauthutil"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type userInfo struct {
|
|
RootFolderID string
|
|
Bucket string
|
|
BridgeUser string
|
|
UserID string
|
|
}
|
|
|
|
type userInfoConfig struct {
|
|
Token string
|
|
}
|
|
|
|
// getUserInfo fetches user metadata from the refresh endpoint
|
|
func getUserInfo(ctx context.Context, cfg *userInfoConfig) (*userInfo, error) {
|
|
// Call the refresh endpoint to get all user metadata
|
|
refreshCfg := internxtconfig.NewDefaultToken(cfg.Token)
|
|
resp, err := internxtauth.RefreshToken(ctx, refreshCfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch user info: %w", err)
|
|
}
|
|
|
|
if resp.User.Bucket == "" {
|
|
return nil, errors.New("API response missing user.bucket")
|
|
}
|
|
if resp.User.RootFolderID == "" {
|
|
return nil, errors.New("API response missing user.rootFolderId")
|
|
}
|
|
if resp.User.BridgeUser == "" {
|
|
return nil, errors.New("API response missing user.bridgeUser")
|
|
}
|
|
if resp.User.UserID == "" {
|
|
return nil, errors.New("API response missing user.userId")
|
|
}
|
|
|
|
info := &userInfo{
|
|
RootFolderID: resp.User.RootFolderID,
|
|
Bucket: resp.User.Bucket,
|
|
BridgeUser: resp.User.BridgeUser,
|
|
UserID: resp.User.UserID,
|
|
}
|
|
|
|
fs.Debugf(nil, "User info: rootFolderId=%s, bucket=%s",
|
|
info.RootFolderID, info.Bucket)
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// parseJWTExpiry extracts the expiry time from a JWT token string
|
|
func parseJWTExpiry(tokenString string) (time.Time, error) {
|
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
|
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("failed to parse token: %w", err)
|
|
}
|
|
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
return time.Time{}, errors.New("invalid token claims")
|
|
}
|
|
|
|
exp, ok := claims["exp"].(float64)
|
|
if !ok {
|
|
return time.Time{}, errors.New("token missing expiration")
|
|
}
|
|
|
|
return time.Unix(int64(exp), 0), nil
|
|
}
|
|
|
|
// jwtToOAuth2Token converts a JWT string to an oauth2.Token with expiry
|
|
func jwtToOAuth2Token(jwtString string) (*oauth2.Token, error) {
|
|
expiry, err := parseJWTExpiry(jwtString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &oauth2.Token{
|
|
AccessToken: jwtString,
|
|
TokenType: "Bearer",
|
|
Expiry: expiry,
|
|
}, nil
|
|
}
|
|
|
|
// computeBasicAuthHeader creates the BasicAuthHeader for bucket operations
|
|
// Following the pattern from SDK's auth/access.go:96-102
|
|
func computeBasicAuthHeader(bridgeUser, userID string) string {
|
|
sum := sha256.Sum256([]byte(userID))
|
|
hexPass := hex.EncodeToString(sum[:])
|
|
creds := fmt.Sprintf("%s:%s", bridgeUser, hexPass)
|
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(creds))
|
|
}
|
|
|
|
// refreshJWTToken refreshes the token using Internxt's refresh endpoint
|
|
func refreshJWTToken(ctx context.Context, name string, m configmap.Mapper) error {
|
|
currentToken, err := oauthutil.GetToken(name, m)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current token: %w", err)
|
|
}
|
|
|
|
cfg := internxtconfig.NewDefaultToken(currentToken.AccessToken)
|
|
resp, err := internxtauth.RefreshToken(ctx, cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("refresh request failed: %w", err)
|
|
}
|
|
|
|
if resp.NewToken == "" {
|
|
return errors.New("refresh response missing newToken")
|
|
}
|
|
|
|
// Convert JWT to oauth2.Token format
|
|
token, err := jwtToOAuth2Token(resp.NewToken)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse refreshed token: %w", err)
|
|
}
|
|
|
|
err = oauthutil.PutToken(name, m, token, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save token: %w", err)
|
|
}
|
|
|
|
if resp.User.Bucket != "" {
|
|
m.Set("bucket", resp.User.Bucket)
|
|
}
|
|
|
|
fs.Debugf(name, "Token refreshed successfully, new expiry: %v", token.Expiry)
|
|
return nil
|
|
}
|