Skip to content

[PM-34387] Add organization invite link creation endpoint#7477

Merged
r-tome merged 39 commits intomainfrom
ac/pm-34387/create-invite-link-command
May 1, 2026
Merged

[PM-34387] Add organization invite link creation endpoint#7477
r-tome merged 39 commits intomainfrom
ac/pm-34387/create-invite-link-command

Conversation

@r-tome
Copy link
Copy Markdown
Contributor

@r-tome r-tome commented Apr 15, 2026

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-34387

📔 Objective

Adds a new POST organizations/{orgId}/invite-link endpoint that lets organization admins create a shareable invite link scoped to specific email domains.

The endpoint is gated behind the generate invite link feature flag and requires the ManageUsers permission like the existing invite users endpoint

@r-tome r-tome marked this pull request as ready for review April 15, 2026 16:12
@r-tome r-tome requested a review from a team as a code owner April 15, 2026 16:12
@r-tome r-tome requested a review from eliykat April 15, 2026 16:12
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 89.47368% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.99%. Comparing base (52d9a9c) to head (dfa3d74).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...nConsole/Controllers/BaseAdminConsoleController.cs 75.00% 4 Missing and 1 partial ⚠️
...Console/OrganizationFeatures/InviteLinks/Errors.cs 50.00% 3 Missing ⚠️
src/Core/Utilities/ValidateSequenceAttribute.cs 81.25% 2 Missing and 1 partial ⚠️
...re/AdminConsole/Entities/OrganizationInviteLink.cs 66.66% 0 Missing and 1 partial ⚠️
...InviteLinks/CreateOrganizationInviteLinkCommand.cs 97.43% 0 Missing and 1 partial ⚠️
...InviteLinks/CreateOrganizationInviteLinkRequest.cs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7477      +/-   ##
==========================================
+ Coverage   63.88%   63.99%   +0.10%     
==========================================
  Files        2088     2096       +8     
  Lines       92383    92534     +151     
  Branches     8205     8220      +15     
==========================================
+ Hits        59019    59214     +195     
+ Misses      31338    31289      -49     
- Partials     2026     2031       +5     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

Logo
Checkmarx One – Scan Summary & Detailsf82bee21-b562-47e0-bd55-05a99ab15352


New Issues (2) Checkmarx found the following issues in this Pull Request
# Severity Issue Source File / Package Checkmarx Insight
1 MEDIUM CSRF src/Api/Billing/Controllers/VNext/AccountBillingVNextController.cs: 146
detailsMethod at line 146 of /src/Api/Billing/Controllers/VNext/AccountBillingVNextController.cs gets a parameter from a user request from request. Thi...
Attack Vector
2 MEDIUM CSRF src/Api/Auth/Controllers/AccountsController.cs: 452
detailsMethod at line 452 of /src/Api/Auth/Controllers/AccountsController.cs gets a parameter from a user request from model. This parameter value flow...
Attack Vector

Fixed Issues (1) Great job! The following issues were fixed in this Pull Request
Severity Issue Source File / Package
MEDIUM CSRF src/Identity/Controllers/AccountsController.cs: 138

@r-tome r-tome added the ai-review-vnext Request a Claude code review using the vNext workflow label Apr 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the new POST organizations/{orgId}/invite-link endpoint, the CreateOrganizationInviteLinkCommand, the OrganizationInviteLink entity helpers, the new ValidateSequenceAttribute<TValidator> utility, and the supporting unit and integration tests. Authorization is enforced via ManageUsersRequirement on the route's {orgId}, with both the feature flag (GenerateInviteLink) and the org ability (UseInviteLinks) checked, providing defense in depth. Domain inputs are validated by the existing DomainNameValidatorAttribute (with MinLength(1) plus the new sequence validator) and then sanitized in the command, and the publicly shareable Code is intentionally a Guid.NewGuid() rather than a comb GUID to avoid leaking timing information.

No new findings. Prior review threads (TOCTOU on the duplicate-link check, domain format validation, duplicate migration scripts, unused InviteLinkInvalidDomains error) have been either addressed or accepted by the team.

Code Review Details

No new findings.

@r-tome r-tome marked this pull request as draft April 15, 2026 16:41
Comment thread src/Core/AdminConsole/Entities/OrganizationInviteLink.cs
@r-tome r-tome marked this pull request as ready for review April 16, 2026 14:16
@r-tome r-tome requested a review from eliykat April 16, 2026 14:16
@r-tome r-tome marked this pull request as draft April 16, 2026 14:25
TimeProvider timeProvider)
: ICreateOrganizationInviteLinkCommand
{
private static readonly DomainNameValidatorAttribute _domainValidator = new();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a data validation attribute designed to be applied to request models. e.g. [DomainNameValidator]. I don't think we should call it manually here. Either:

  • put it on the api request model so the api handles validation, or
  • extract its logic to a static class that can be called in different contexts

We have a bit of an inconsistency in where/how we do validation. I think this is OK to do at the api layer as it's simple validation on the input value rather than more complex business rules usually checked by the core layer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have moved the logic over to CreateOrganizationInviteLinkRequestModel.Validate method. I couldn't use the attribute simply because its a list of domains.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(non-blocking) 💭 You could create a generic validator that takes another validator and applies it to each item in a list. e.g. ValidateSequence<DomainNameValidatorAttribute>. Your solution is also valid.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is a great idea! Gave it a go

Comment thread src/Core/AdminConsole/Entities/OrganizationInviteLink.cs Outdated
@r-tome r-tome changed the base branch from main to ac/pm-35253/add-useinvitelinks-organization-ability April 22, 2026 15:16
Comment thread src/Core/AdminConsole/OrganizationFeatures/InviteLinks/Errors.cs Outdated
Comment thread src/Core/AdminConsole/OrganizationFeatures/InviteLinks/Errors.cs Outdated
@r-tome r-tome marked this pull request as ready for review April 22, 2026 16:54
@r-tome r-tome requested a review from eliykat April 22, 2026 16:54
@r-tome
Copy link
Copy Markdown
Contributor Author

r-tome commented Apr 22, 2026

The integration test is failing because the Pricing service hasn't been deployed on QA yet.

Copy link
Copy Markdown
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

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

LGTM once the base PR is merged, aside from 1 question about the test.

Comment on lines +39 to +44
_factory.SubstituteService<IApplicationCacheService>(cacheService =>
{
cacheService
.GetOrganizationAbilityAsync(Arg.Any<Guid>())
.Returns(new OrganizationAbility { UseInviteLinks = true });
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm interested why this is necessary - the signup helper should upsert the new org into the cache. We don't want to have to mock this behavior for every single integration test that may check the cache. Can you please look into this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

#7477 (comment)

I think this is the reason. QA pricing service hasn't updated yet so it does not return UseInviteLinks therefore it defaults to false. I just mocked it so we wouldn't depend on QA env.

@eliykat eliykat self-requested a review April 29, 2026 03:08
Comment thread src/Core/Utilities/ValidateSequenceAttribute.cs Outdated
Comment thread src/Core/Utilities/ValidateSequenceAttribute.cs Outdated
Comment thread src/Core/Utilities/ValidateSequenceAttribute.cs Outdated
eliykat
eliykat previously approved these changes Apr 29, 2026
return ValidationResult.Success;
}

var items = ((IEnumerable)value).Cast<object>().ToList();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will throw, but maybe not with a helpful error message. I was thinking more like:

Suggested change
var items = ((IEnumerable)value).Cast<object>().ToList();
if (value is not IEnumerable<object> items)
{
throw new ArgumentException("ValidateSequenceAttribute can only be used with an IEnumerable");
}

{
if (value is null)
{
return ValidationResult.Success;
Copy link
Copy Markdown
Member

@eliykat eliykat Apr 30, 2026

Choose a reason for hiding this comment

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

It's not clear whether an empty list should pass validation or not. In our case it's invalid because the user is meant to supply at least 1 domain name, but it really depends on the business logic.

I think your approach is fine because it matches the behaviour of LINQ .All(). But we should mention it in the xmldoc. e.g. An empty list will always pass validation. Add additional validation if an empty list is invalid. This is our situation because your command checks it, and/or you can use minLength. It just needs to be made clear to callers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Clarified that in the xmldoc

Base automatically changed from ac/pm-35253/add-useinvitelinks-organization-ability to main April 30, 2026 09:13
@r-tome r-tome dismissed eliykat’s stale review April 30, 2026 09:13

The base branch was changed.

@r-tome r-tome requested a review from a team as a code owner April 30, 2026 09:13
r-tome added 16 commits April 30, 2026 12:42
…InviteLinkCommand

- Introduced a new method to verify if an organization can use invite links based on its ability.
- Added a new error type for cases where invite links are not available due to organizational plan restrictions.
- Updated tests to cover scenarios where the organization lacks the ability to create invite links.
- Added XML summary comments to the Code property to clarify its purpose and generation method.
- Explained the choice of using Guid.NewGuid for the Code to avoid predictability and ensure uniqueness.
- Added IValidatableObject implementation to CreateOrganizationInviteLinkRequestModel for domain validation.
- Introduced Validate method to check the format of allowed domains and return appropriate validation results.
- Updated tests to cover scenarios for invalid domain formats and mixed valid/invalid domains.
- Removed redundant domain validation logic from CreateOrganizationInviteLinkCommand.
- Deleted tests for validating EncryptedInviteKey and EncryptedOrgKey as they are no longer relevant.
- Cleaned up the test class to focus on current validation logic for allowed domains.
- Updated the GetAllowedDomains method to return an empty array instead of throwing a JsonException when deserialization fails.
- This change improves the method's resilience by providing a default value for invalid or missing allowed domains.
- Deleted the InviteLinkInvalidDomains record as it is no longer needed.
- This cleanup aligns with recent changes in domain validation logic and improves code maintainability.
…r command registration

- Changed the registration of ICreateOrganizationInviteLinkCommand to use TryAddScoped instead of AddScoped.
…enceAttribute for domain validation and update unit tests for improved error handling.
…eLinkRequestModel and implement unit test for empty AllowedDomains scenario
@r-tome r-tome force-pushed the ac/pm-34387/create-invite-link-command branch from bae625e to 73a02de Compare April 30, 2026 12:12
@r-tome r-tome removed request for a team and kdenney April 30, 2026 12:13
@sonarqubecloud
Copy link
Copy Markdown

@r-tome r-tome requested a review from eliykat April 30, 2026 13:38
@r-tome r-tome marked this pull request as ready for review April 30, 2026 13:38
@r-tome r-tome merged commit 2a52362 into main May 1, 2026
70 of 71 checks passed
@r-tome r-tome deleted the ac/pm-34387/create-invite-link-command branch May 1, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants