From 94829aaec5c2d190c3f0d4fead860502b8255aa7 Mon Sep 17 00:00:00 2001 From: Microscotch Date: Mon, 6 Oct 2025 17:18:38 +0200 Subject: [PATCH] proton: automated 2FA login with OTP secret key add OTP secret key to config to generate 2FA code --- backend/protondrive/protondrive.go | 30 ++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 ++++ 3 files changed, 36 insertions(+) diff --git a/backend/protondrive/protondrive.go b/backend/protondrive/protondrive.go index 821e3f9c7..a770143eb 100644 --- a/backend/protondrive/protondrive.go +++ b/backend/protondrive/protondrive.go @@ -13,6 +13,8 @@ import ( protonDriveAPI "github.com/henrybear327/Proton-API-Bridge" "github.com/henrybear327/go-proton-api" + "github.com/pquerna/otp/totp" + "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/config/configmap" @@ -87,6 +89,17 @@ The value can also be provided with --protondrive-2fa=000000 The 2FA code of your proton drive account if the account is set up with two-factor authentication`, Required: false, + }, { + Name: "otp_secret_key", + Help: `The OTP secret key + +The value can also be provided with --protondrive-otp-secret-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 + +The OTP secret key of your proton drive account if the account is set up with +two-factor authentication`, + Required: false, + Sensitive: true, + IsPassword: true, }, { Name: clientUIDKey, Help: "Client uid key (internal use only)", @@ -191,6 +204,7 @@ type Options struct { Password string `config:"password"` MailboxPassword string `config:"mailbox_password"` TwoFA string `config:"2fa"` + OtpSecretKey string `config:"otp_secret_key"` // advanced Enc encoder.MultiEncoder `config:"encoding"` @@ -356,7 +370,15 @@ func newProtonDrive(ctx context.Context, f *Fs, opt *Options, m configmap.Mapper config.FirstLoginCredential.Username = opt.Username config.FirstLoginCredential.Password = opt.Password config.FirstLoginCredential.MailboxPassword = opt.MailboxPassword + // if 2FA code is provided, use it; otherwise, generate one using the OTP secret key if provided config.FirstLoginCredential.TwoFA = opt.TwoFA + if opt.TwoFA == "" && opt.OtpSecretKey != "" { + code, err := totp.GenerateCode(opt.OtpSecretKey, time.Now()) + if err != nil { + return nil, fmt.Errorf("couldn't generate 2FA code: %w", err) + } + config.FirstLoginCredential.TwoFA = code + } protonDrive, auth, err := protonDriveAPI.NewProtonDrive(ctx, config, authHandler, deAuthHandler) if err != nil { return nil, fmt.Errorf("couldn't initialize a new proton drive instance: %w", err) @@ -395,6 +417,14 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e } } + if opt.OtpSecretKey != "" { + var err error + opt.OtpSecretKey, err = obscure.Reveal(opt.OtpSecretKey) + if err != nil { + return nil, fmt.Errorf("couldn't decrypt OtpSecretKey: %w", err) + } + } + ci := fs.GetConfig(ctx) root = strings.Trim(root, "/") diff --git a/go.mod b/go.mod index 7fe07e804..bbcb62d05 100644 --- a/go.mod +++ b/go.mod @@ -127,6 +127,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/bradenaw/juniper v0.15.3 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect @@ -246,6 +247,7 @@ require ( github.com/ProtonMail/go-crypto v1.3.0 github.com/golang-jwt/jwt/v4 v4.5.2 github.com/pkg/xattr v0.4.12 + github.com/pquerna/otp v1.5.0 golang.org/x/mobile v0.0.0-20250911085028-6912353760cf golang.org/x/term v0.35.0 ) diff --git a/go.sum b/go.sum index 155439988..99d381d8d 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,8 @@ github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradenaw/juniper v0.15.3 h1:RHIAMEDTpvmzV1wg1jMAHGOoI2oJUSPx3lxRldXnFGo= github.com/bradenaw/juniper v0.15.3/go.mod h1:UX4FX57kVSaDp4TPqvSjkAAewmRFAfXf27BOs5z9dq8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= @@ -512,6 +514,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= +github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=