Skip to content

Conversation

@harr1424
Copy link
Contributor

@harr1424 harr1424 commented Dec 24, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-24618
https://bitwarden.atlassian.net/browse/PM-23109
bitwarden/server#5895

📔 Objective

PM-24618: reveal the functionality related to email based OTP in the CLI's send help output bw send --help
PM-23109: update models to add an AuthType enum and ensure the process of creating and receiving a send supports storing emails associated with the send to be used for email based OTP authentication and the AuthType enum value

💻 Command Output

john@Johns-MacBook-Pro clients % bw send --help
Usage: bw send [options] [command] <data>

Work with Bitwarden sends. A Send can be quickly created using this command or subcommands can be used to fine-tune the Send

Arguments:
  data                            The data to Send. Specify as a filepath with the --file option

Options:
  -f, --file                      Specifies that <data> is a filepath
  -d, --deleteInDays <days>       The number of days in the future to set deletion date, defaults to 7 (default: "7")
  --password <password>           optional password to access this Send. Can also be specified in JSON.
  --email <email>                 optional emails to access this Send. Can also be specified in JSON.
  -a, --maxAccessCount <amount>   The amount of max possible accesses.
  --hidden                        Hide <data> in web by default. Valid only if --file is not set.
  -n, --name <name>               The name of the Send. Defaults to a guid for text Sends and the filename for files.
  --notes <notes>                 Notes to add to the Send.
  --fullObject                    Specifies that the full Send object should be returned rather than just the access url.
  -h, --help                      display help for command

Commands:
  list                            List all the Sends owned by you
  template <object>               Get json templates for send objects
  get [options] <id>              Get Sends owned by you.
  receive [options] <url>         Access a Bitwarden Send from a url
  create [options] [encodedJson]  create a Send
  edit [options] [encodedJson]    edit a Send
  remove-password <id>            removes the saved password from a Send.
  delete <id>                     delete a Send
john@Johns-MacBook-Pro clients % bw send list
[{"object":"send","id":"b30db19a-b7dd-4868-b240-b3c000241108","accessId":"mrENs923aEiyQLPAACQRCA","accessUrl":"https://vault.bitwarden.com/#/send/mrENs923aEiyQLPAACQRCA/SGPAqktzOnHM_H9eSba67g","name":"096b5c3f-a629-4a9f-b6bb-4c913c0df35c","notes":null,"key":"SGPAqktzOnHM/H9eSba67g==","type":0,"maxAccessCount":null,"accessCount":0,"revisionDate":"2025-12-27T02:11:17.647Z","deletionDate":"2026-01-03T02:11:01.097Z","expirationDate":null,"passwordSet":false,"emails":["[email protected]"],"disabled":false,"hideEmail":false,"authType":"Email","text":{"text":"hello world","hidden":false}},

{"object":"send","id":"fc9990eb-ad93-4f2b-a427-b3c00025a65f","accessId":"65CZ_JOtK0-kJ7PAACWmXw","accessUrl":"https://vault.bitwarden.com/#/send/65CZ_JOtK0-kJ7PAACWmXw/MXzuEPT0zX3vc7RRDdvZfw","name":"e91ec017-1915-4b1f-b80e-7d8e57657fbc","notes":null,"key":"MXzuEPT0zX3vc7RRDdvZfw==","type":0,"maxAccessCount":null,"accessCount":0,"revisionDate":"2025-12-27T02:17:04.634Z","deletionDate":"2026-01-03T02:17:04.322Z","expirationDate":null,"passwordSet":false,"emails":["[email protected]"],"disabled":false,"hideEmail":false,"authType":"Email","text":{"text":"hello world","hidden":false}}]
john@Johns-MacBook-Pro clients % bw send get fc9990eb-ad93-4f2b-a427-b3c00025a65f
{"object":"send","id":"fc9990eb-ad93-4f2b-a427-b3c00025a65f","accessId":"65CZ_JOtK0-kJ7PAACWmXw","accessUrl":"https://vault.bitwarden.com/#/send/65CZ_JOtK0-kJ7PAACWmXw/MXzuEPT0zX3vc7RRDdvZfw","name":"e91ec017-1915-4b1f-b80e-7d8e57657fbc","notes":null,"key":"MXzuEPT0zX3vc7RRDdvZfw==","type":0,"maxAccessCount":null,"accessCount":0,"revisionDate":"2025-12-27T02:17:04.634Z","deletionDate":"2026-01-03T02:17:04.322Z","expirationDate":null,"passwordSet":false,"emails":["[email protected]"],"disabled":false,"hideEmail":false,"authType":"Email","text":{"text":"hello world","hidden":false}}

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions
Copy link
Contributor

github-actions bot commented Dec 24, 2025

Logo
Checkmarx One – Scan Summary & Details081736a3-16f5-4d1f-befd-2915dcbe623e

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Dec 24, 2025

Codecov Report

❌ Patch coverage is 85.29412% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.67%. Comparing base (c6f704b) to head (51f920d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
apps/cli/src/tools/send/commands/edit.command.ts 91.66% 0 Missing and 1 partial ⚠️
apps/cli/src/tools/send/send.program.ts 0.00% 1 Missing ⚠️
...on/src/tools/send/models/response/send.response.ts 0.00% 1 Missing ⚠️
...ibs/common/src/tools/send/models/view/send.view.ts 66.66% 0 Missing and 1 partial ⚠️
...ibs/common/src/tools/send/services/send.service.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #18106      +/-   ##
==========================================
+ Coverage   42.50%   42.67%   +0.16%     
==========================================
  Files        3624     3624              
  Lines      105022   105046      +24     
  Branches    15844    15854      +10     
==========================================
+ Hits        44641    44829     +188     
+ Misses      58495    58305     -190     
- Partials     1886     1912      +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@harr1424 harr1424 changed the title WIP explore existing Send attributes and logic [PM-24618] Integrate CLI sends with server branch tools/pm-21918/send-authentication-commands Dec 27, 2025
@harr1424 harr1424 marked this pull request as ready for review December 27, 2025 02:37
@harr1424 harr1424 requested a review from a team as a code owner December 27, 2025 02:37
@harr1424 harr1424 added the ai-review Request a Claude code review label Dec 27, 2025
mcamirault
mcamirault previously approved these changes Dec 29, 2025
Copy link
Contributor

@mcamirault mcamirault left a comment

Choose a reason for hiding this comment

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

LGTM

@harr1424 harr1424 added the needs-qa Marks a PR as requiring QA approval label Dec 29, 2025
@harr1424 harr1424 changed the title [PM-24618] Integrate CLI sends with server branch tools/pm-21918/send-authentication-commands [PM-24618] Integrate CLI sends with server branch Dec 29, 2025
@claude
Copy link
Contributor

claude bot commented Dec 29, 2025

Claude finished @harr1424's task in 4m 48s —— View job


Overall Assessment: APPROVE

This PR successfully integrates email-based OTP authentication for CLI sends, adding the AuthType enum following ADR-0025 patterns, mutual exclusivity validation between emails and password, and comprehensive test coverage (765 lines of new tests). The implementation correctly handles authType inference and data flow through all model layers.

Code Review Details
  • QUESTION: Should AuthType enum be defined in SDK instead?
    • libs/common/src/tools/send/models/domain/send.ts:24
    • Currently defined in clients repo, but shared types are typically in SDK for cross-repository consistency

Additional Observations

Positive aspects:

  • AuthType enum correctly follows ADR-0025 no-enums pattern (matches SendType implementation)
  • Mutual exclusivity validation is thorough and handles edge cases (empty strings, whitespace, empty arrays)
  • Test coverage is excellent with 365 lines for create command and 400 lines for edit command
  • Email parsing via parseEmail utility is well-implemented with comprehensive validation
  • Data flow through SendResponse → SendData → Send → SendView is consistent

Future improvements (not blocking):

  • Consider adding authType and emails to template method in send.response.ts for better feature discoverability via bw send template
  • SendView.fromJSON could be enhanced to restore authType and emails properties (currently only toJSON includes them)

Code coverage gaps (per Codecov):

  • send.view.ts: 40% patch coverage (2 lines + 1 partial) - primarily fromJSON/toJSON methods
  • send.program.ts: 0% (1 line) - CLI setup code
  • edit.command.ts: 92.85% (1 partial)
  • send.response.ts (common): 0% (1 line)

🤖 Generated with Claude Code

req.maxAccessCount = maxAccessCount;
req.emails = emails;

if (emails != null && emails.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: Unsafe array length check on potentially undefined value

Details and fix

Line 82 extracts emails with ?? undefined, but line 93 checks emails.length > 0 without first verifying emails is not undefined. This will throw TypeError: Cannot read property 'length' of undefined if emails is undefined.

Fix:

if (emails != null && emails.length > 0) {
  req.authType = AuthType.Email;
} else if (password != null && password.trim().length > 0) {
  req.authType = AuthType.Password;
} else {
  req.authType = AuthType.None;
}

Why: JavaScript/TypeScript will throw when accessing .length on undefined. The nullish coalescing on line 82 explicitly returns undefined, making this a runtime error waiting to happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Loose equality operator != will handle undefined as intended here, which is likely why the suggested "fix" is identical to the existing code.

this.deletionDate = o.deletionDate;
this.expirationDate = o.expirationDate;
this.passwordSet = o.password != null;
this.emails = o.emails ?? [];
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL: Missing authType assignment in constructor causes data loss

Details and fix

The constructor (lines 98-132) creates a SendResponse from a SendView but doesn't copy the authType field. This is added at line 124, but the corresponding field in the toView static method (line 36-58) is also missing authType assignment.

Current state:

  • Line 124: this.authType = o.authType; ✓ (constructor copies it)
  • Line 55: view.emails = send.emails ?? [];
  • Line 58: Missing view.authType = send.authType;

Fix needed in toView method around line 57:

view.emails = send.emails ?? [];
view.disabled = send.disabled;
view.hideEmail = send.hideEmail;
view.authType = send.authType;  // Add this line
return view;

Impact: The toView method is used in create.command.ts line 147. Without copying authType, sends lose their authentication type during the create flow.


req.key = null;
req.maxAccessCount = maxAccessCount;
req.emails = emails;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ IMPORTANT: authType calculated but never assigned to encrypted send before API call

Details and fix

Lines 93-99 calculate req.authType based on whether emails or password is provided, but this value is never transferred to encSend before the API save at line 154.

Looking at line 152: encSend.emails = emails && emails.join(","); - the emails are assigned, but authType is not.

Fix needed around line 152:

encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
encSend.emails = emails && emails.join(",");
encSend.authType = req.authType;  // Add this

Impact: The server receives sends without the authType field, potentially breaking email-based OTP authentication or causing it to default incorrectly.

@harr1424 harr1424 marked this pull request as draft December 29, 2025 22:55
@harr1424 harr1424 marked this pull request as ready for review December 30, 2025 15:55
Comment on lines +66 to +71
const hasEmails = req.emails != null && req.emails.length > 0;
const hasPassword = req.password != null && req.password !== "";

if (hasEmails && hasPassword) {
return Response.badRequest("--password and --emails are mutually exclusive.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 SUGGESTED: Apply this same validation pattern to create.command for consistency

Details

This validation correctly checks for meaningful presence (length > 0 for emails, non-empty string for password) rather than just checking if values are defined.

However, create.command.ts line 85 uses a different pattern:

if (emails !== undefined && password !== undefined) {
  return Response.badRequest("--password and --emails are mutually exclusive.");
}

This creates inconsistent behavior:

  • edit: Allows {"emails": [], "password": "foo"} (correct per tests)
  • create: Rejects {"emails": [], "password": "foo"} (incorrect per create.command.spec.ts line 287-306)

Recommendation: Update create.command line 85-86 to use the same validation pattern you've implemented here:

const hasEmails = emails != null && emails.length > 0;
const hasPassword = password != null && password.trim().length > 0;

if (hasEmails && hasPassword) {
  return Response.badRequest("--password and --emails are mutually exclusive.");
}

This ensures both commands treat empty arrays and whitespace-only passwords consistently as "no auth specified".

@harr1424 harr1424 removed the ai-review Request a Claude code review label Dec 30, 2025
Copy link
Contributor

@mcamirault mcamirault left a comment

Choose a reason for hiding this comment

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

Overall looks good, though I believe the Create command tests aren't checking what they should

Copy link
Contributor

@mcamirault mcamirault left a comment

Choose a reason for hiding this comment

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

You addressed all my concerns, looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-qa Marks a PR as requiring QA approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants