Skip to content

⚗️ Transfer with recipients#7

Merged
HanXHX merged 1 commit intomasterfrom
transfer_with_recipients
Feb 25, 2026
Merged

⚗️ Transfer with recipients#7
HanXHX merged 1 commit intomasterfrom
transfer_with_recipients

Conversation

@HanXHX
Copy link
Copy Markdown
Contributor

@HanXHX HanXHX commented Feb 25, 2026

No description provided.

Copilot AI review requested due to automatic review settings February 25, 2026 09:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the transfer creation flow to support specifying recipient email addresses and leverages returned recipient public keys to reduce/avoid requiring a transfer passphrase when recipients already have keys.

Changes:

  • Extend POST /share request/response handling to include recipient emails and returned public_keys.
  • Add --to (repeatable) to retyc transfer create and display recipients in the confirmation summary.
  • Adjust encryption/passphrase flow to encrypt the session key for owner + keyed recipients, and only require prompting for a passphrase when needed.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
internal/api/transfer.go Adds public_keys to ShareCreateResponse and threads recipient emails through CreateShare.
cmd/transfer.go Adds --to recipients flag, parallelizes key/share creation, and updates passphrase + encryption behavior based on which recipients have keys.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/transfer.go
Comment on lines +405 to 438
// Fetch the user's own public key and create the share in parallel.
var titlePtr *string
if title != "" {
titlePtr = &title
}
if passphrase == "" {
return fmt.Errorf("transfer passphrase is required")

type keyResult struct {
v *api.UserKey
err error
}
type shareResult struct {
v *api.ShareCreateResponse
err error
}
keyCh := make(chan keyResult, 1)
shareCh := make(chan shareResult, 1)

// Fetch the user's own public key so recipients of transfer info can decrypt it later.
userKey, err := client.GetActiveKey(ctx)
if err != nil {
return fmt.Errorf("fetching encryption key: %w", err)
go func() {
v, err := client.GetActiveKey(ctx)
keyCh <- keyResult{v, err}
}()
go func() {
v, err := client.CreateShare(ctx, expire, titlePtr, true, toEmails)
shareCh <- shareResult{v, err}
}()

kr := <-keyCh
if kr.err != nil {
return fmt.Errorf("fetching encryption key: %w", kr.err)
}
userKey := kr.v
if userKey == nil {
return fmt.Errorf("no active encryption key — set up your key in the web interface first")
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating the share concurrently with GetActiveKey means that if GetActiveKey fails (or returns nil), the goroutine calling CreateShare may still have created a transfer on the server, and the command then returns an error without cleaning it up. Consider fetching the active key first (then creating the share), or using a cancellable context and disabling/deleting the created share on early failure to avoid leaving orphan transfers behind.

Copilot uses AI. Check for mistakes.
Comment thread cmd/transfer.go
Comment on lines +447 to +470
// Decide whether a transfer passphrase is needed.
// A passphrase is not needed only when all specified recipients already have a key.
allHaveKeys := len(toEmails) > 0 && len(share.PublicKeys) == len(toEmails)
needPassphrase := !allHaveKeys || passphraseExplicit

// Inform the user if some recipients have no key and a passphrase is therefore required.
if len(toEmails) > 0 && len(share.PublicKeys) < len(toEmails) {
fmt.Fprintf(os.Stderr, "Note: %d recipient(s) have no encryption key — a transfer passphrase is required.\n",
len(toEmails)-len(share.PublicKeys))
}

// Prompt for transfer passphrase if required and not provided via flag.
if needPassphrase && passphrase == "" {
fmt.Fprint(os.Stderr, "Transfer passphrase: ")
pb, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprint(os.Stderr, "\r\033[2K")
if err != nil {
return fmt.Errorf("reading passphrase: %w", err)
}
passphrase = string(pb)
}
if needPassphrase && passphrase == "" {
return fmt.Errorf("transfer passphrase is required")
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateShare is executed before prompting for a required transfer passphrase. If the passphrase is required but cannot be read (non-interactive run, user aborts, etc.), the command returns an error after having created a transfer on the server but before uploading/completing it. Consider prompting/validating the passphrase earlier when it will be required, or explicitly disabling the share on this failure path once share.ID is known.

Copilot uses AI. Check for mistakes.
@HanXHX HanXHX force-pushed the transfer_with_recipients branch from cf5ca79 to d0c383f Compare February 25, 2026 10:22
@HanXHX HanXHX force-pushed the transfer_with_recipients branch from d0c383f to af0bd18 Compare February 25, 2026 10:25
@HanXHX
Copy link
Copy Markdown
Contributor Author

HanXHX commented Feb 25, 2026

@copilot recheck, new code added, force push done

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@HanXHX I've opened a new pull request, #9, to work on those changes. Once the pull request is ready, I'll request review from you.

@HanXHX HanXHX merged commit 42c0e43 into master Feb 25, 2026
4 checks passed
@HanXHX HanXHX deleted the transfer_with_recipients branch February 25, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants