* feat(admin-console): Add bulk confirmation and pending auto-confirmation methods for organization users
- Implemented ConfirmManyOrganizationUsersAsync to confirm multiple users in a single operation.
- Added GetManyPendingAutoConfirmAsync to retrieve users pending automatic confirmation.
- Created stored procedures for bulk confirmation and fetching pending users.
- Updated relevant repository interfaces and implementations across Dapper and Entity Framework.
* refactor(admin-console): Change parameter type for ConfirmManyOrganizationUsersAsync to IReadOnlyCollection
- Updated the ConfirmManyOrganizationUsersAsync method signature in the IOrganizationUserRepository and its implementations to use IReadOnlyCollection instead of IEnumerable for better performance and clarity.
- Adjusted related repository methods in both Dapper and Entity Framework implementations to reflect this change.
- Added unit tests to ensure the new implementation behaves as expected, including scenarios for mixed batches and idempotency.
* Remove OrganizationUser_ReadByOrganizationIdStatus stored procedure as part of database cleanup.
* Add integration tests for ConfirmManyOrganizationUsers and GetManyPendingAutoConfirm methods
- Introduced ConfirmManyOrganizationUsersTests to validate the confirmation of multiple organization users, ensuring only accepted users are confirmed and idempotency is maintained.
- Added GetManyPendingAutoConfirmTests to verify retrieval of pending auto-confirm users, ensuring only eligible users are returned based on specific criteria.
- Removed duplicate test implementations from OrganizationUserRepositoryTests to maintain clarity and organization in the test suite.
* Implement OrganizationUser_UpdateStatusKey stored procedure and update related repository method
- Added OrganizationUser_UpdateStatusKey stored procedure to handle updating the status and key of organization users based on a JSON input.
- Updated OrganizationUserRepository to call the new stored procedure instead of the previous confirmation procedure.
- Modified OrganizationUser_ReadByPendingAutoConfirm stored procedure to filter users by a new type value.
- Enhanced integration tests to verify the correct behavior of the updated confirmation logic, ensuring revision dates are accurately tracked.
* Refactor OrganizationUser_UpdateStatusKey to OrganizationUser_UpdateManyStatusKey
- Renamed the stored procedure to OrganizationUser_UpdateManyStatusKey to better reflect its functionality of updating multiple organization users' statuses.
- Updated the OrganizationUserRepository to call the new stored procedure.
- Adjusted the migration script to create or alter the procedure accordingly.
* Update data type for Key column in AddOrganizationUserUpdateStatusKey migration script
- Changed the data type of the Key column from NVARCHAR(MAX) to VARCHAR(MAX) in the UsersToUpdate table and the corresponding JSON parsing logic to improve compatibility and performance.
* Updated spacing
* Add stored procedures for organization user status updates and retrieval
- Created OrganizationUser_UpdateManyStatusKey to update multiple organization users' statuses based on a JSON input, including handling revision dates and tracking updated IDs for idempotency.
- Added OrganizationUser_ReadByPendingAutoConfirm to retrieve organization users pending auto-confirmation based on organization ID and specific status and type filters.
---------
Co-authored-by: mkincaid-bw <mkincaid@bitwarden.com>
* [PM-36949] Add OrganizationPlanMigrationCohort schema and Core domain types
Add the foundation for cohort-based plan migrations:
- Two tables: OrganizationPlanMigrationCohort and OrganizationPlanMigrationCohortAssignment
- Two views and nine stored procedures (four CRUD on cohort, four CRUD plus
ReadByOrganizationId on assignment)
- Single Migrator script for MSSQL deployment
- Core entities, MigrationPath value object and its registry, and bare repository
interfaces under Bit.Core.Billing.Organizations.PlanMigration
The cohort table holds the human-managed metadata (name, discount coupons,
MigrationPathId byte) and the assignment table records each organization's
position in the migration lifecycle (scheduled, migrated, churn-mitigated).
Both Update SPs follow the accept-but-don't-assign pattern: immutable columns
(OrganizationId, CohortId, CreatedAt) are parameters but not SET clauses.
* [PM-36949] Add Dapper repositories for plan migration cohort tables
OrganizationPlanMigrationCohortRepository inherits the base Repository<T, TId>
CRUD methods unchanged. OrganizationPlanMigrationCohortAssignmentRepository
also relies on the base for CRUD and adds GetByOrganizationIdAsync which
returns at most one row (the UNIQUE constraint on OrganizationId at the
database layer guarantees this).
* [PM-36949] Add EF Core configurations, repositories, and provider migrations for plan migration cohort tables
- EF models wrap the Core entities; the assignment model exposes nav properties
for Organization and Cohort so the FK + cascade-delete is inferred by EF.
- EntityTypeConfiguration classes pin ID generation to application code
(ValueGeneratedNever) and declare the UNIQUE indexes plus the composite
(CohortId, ScheduledAt, MigratedAt) index.
- Repositories follow the OrganizationInstallationRepository template; the
assignment repo adds GetByOrganizationIdAsync to mirror the SP exposed on
the MSSQL side.
- DatabaseContext gets two DbSet properties; auto-discovery picks up the
configuration classes.
- Generated migrations for MySQL, Postgres, and SQLite create matching schemas;
EF truncates FK and index names on providers with 64-char identifier limits,
which is consistent with the rest of the codebase.
* [PM-36949] Wire up DI and add tests for plan migration cohort repositories
Register both Dapper and EF Core repositories in their respective service
collection extensions, following the existing AddSingleton convention in
these files.
Add tests:
- MigrationPathIdsSnapshotTests guards the immortal byte IDs that downstream
code pins on. The class- and method-level comments document why these
values can never be renumbered.
- MigrationPathTests covers the FromId round-trip and the null-on-unknown
behavior the registry promises to callers.
- OrganizationPlanMigrationCohortRepositoryTests exercises CRUD, the UNIQUE
Name constraint, and verifies that ReplaceAsync ignores CreatedAt
mutations (per the accept-but-don't-assign Update SP).
- OrganizationPlanMigrationCohortAssignmentRepositoryTests exercises CRUD,
GetByOrganizationIdAsync, the UNIQUE OrganizationId constraint,
cascade-delete from both Organization and Cohort, and verifies that
ReplaceAsync ignores OrganizationId, CohortId, and CreatedAt mutations.
* [PM-36949] Use PlanType and MigrationPathId enums on MigrationPath
Replace the byte Id with a byte-backed MigrationPathId enum and replace
the string FromPlan/ToPlan fields with PlanType. Persistence is
unchanged -- EF normalises enum-backed properties to their underlying
type in the model snapshot, and Dapper handles enum-to-byte parameter
mapping automatically.
* [PM-36949] Add *.lscache to .gitignore
* [PM-36949] fix: Override ReplaceAsync on EF cohort repositories for immutability parity
The Dapper _Update SPs accept-but-don't-assign certain columns (CreatedAt
on cohort; OrganizationId, CohortId, and CreatedAt on assignment), but
the base EF Repository<T,TEntity,Guid>.ReplaceAsync uses SetValues which
writes every scalar. Override on both repos and mark the immutable
properties as IsModified = false so MySQL/Postgres/Sqlite match MSSQL
behavior. Mirrors the existing DeviceRepository.ReplaceAsync pattern.
* [PM-36949] fix: Bound cohort string columns and widen Name to 255 chars
Add [MaxLength] attributes to the three cohort string properties so the
EF providers (MySQL/Postgres/Sqlite) enforce the same limits as MSSQL,
where the columns were already NVARCHAR-capped. Widen Name from 64 to
255 chars across MSSQL DDL, both _Create/_Update SP signatures, the
Migrator script, the entity, and all three regenerated EF migrations.
Coupon codes stay at 64 (Stripe IDs are short).
* [PM-36949] test: Lock FromPlan and ToPlan per MigrationPath
The snapshot test class doc says "byte N means a specific FromPlan ->
ToPlan transition forever", but only the byte value was being asserted.
A silent refactor of the registry's PlanType references would not have
been caught. Add per-path FromPlan/ToPlan assertions to close the gap.
* [PM-36949] chore: Apply dotnet format
* [PM-36949] test: Use LaxDateTimeComparer for round-tripped DateTimes
Postgres timestamp and MySQL datetime(6) store microsecond precision (6
fractional digits), but .NET DateTime is 100ns ticks (7 digits). Exact
Assert.Equal fails by a single tick on round-trip. Switch the three
DateTime comparisons in ReplaceAsync_UpdatesMutableColumns_AndIgnoresImmutableOnes
to LaxDateTimeComparer.Default -- the same 2ms-tolerance comparer used
by SendRepositoryTests and InstallationRepositoryTests for the same
precision issue.
* Add implementation for dropdown
* Reconcile dropdown work with renamed PM-36949 schema
After merging origin/main, the cohort schema now uses *Date suffixes
(ScheduledDate / MigratedDate / ChurnDiscountAppliedDate / CreationDate).
Rename references in the dropdown work, restore IsLocked() on the merged
entity, and re-add GetManyAsync() to the cohort repository (interface +
Dapper + EF) since the merge took main's pre-dropdown versions.
Also remove the six superseded provider migration files (20260515*) -- the
20260518* renames from origin/main are now the only cohort migrations.
* Fix UTF-8 BOM on cohort assignment unit test file
* Resolve the FF and permission issue
* Extract migration cohort resolution into a helper
Addresses PR feedback to keep the Edit action focused: the cohort
resolution/validation now lives in ResolveMigrationCohortAssignmentChangeAsync
and returns a MigrationCohortAssignmentChange record. The endpoint still
owns the page return.
---------
Co-authored-by: Alex Morask <amorask@bitwarden.com>
Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com>
* [PM-36949] Add OrganizationPlanMigrationCohort schema and Core domain types
Add the foundation for cohort-based plan migrations:
- Two tables: OrganizationPlanMigrationCohort and OrganizationPlanMigrationCohortAssignment
- Two views and nine stored procedures (four CRUD on cohort, four CRUD plus
ReadByOrganizationId on assignment)
- Single Migrator script for MSSQL deployment
- Core entities, MigrationPath value object and its registry, and bare repository
interfaces under Bit.Core.Billing.Organizations.PlanMigration
The cohort table holds the human-managed metadata (name, discount coupons,
MigrationPathId byte) and the assignment table records each organization's
position in the migration lifecycle (scheduled, migrated, churn-mitigated).
Both Update SPs follow the accept-but-don't-assign pattern: immutable columns
(OrganizationId, CohortId, CreatedAt) are parameters but not SET clauses.
* [PM-36949] Add Dapper repositories for plan migration cohort tables
OrganizationPlanMigrationCohortRepository inherits the base Repository<T, TId>
CRUD methods unchanged. OrganizationPlanMigrationCohortAssignmentRepository
also relies on the base for CRUD and adds GetByOrganizationIdAsync which
returns at most one row (the UNIQUE constraint on OrganizationId at the
database layer guarantees this).
* [PM-36949] Add EF Core configurations, repositories, and provider migrations for plan migration cohort tables
- EF models wrap the Core entities; the assignment model exposes nav properties
for Organization and Cohort so the FK + cascade-delete is inferred by EF.
- EntityTypeConfiguration classes pin ID generation to application code
(ValueGeneratedNever) and declare the UNIQUE indexes plus the composite
(CohortId, ScheduledAt, MigratedAt) index.
- Repositories follow the OrganizationInstallationRepository template; the
assignment repo adds GetByOrganizationIdAsync to mirror the SP exposed on
the MSSQL side.
- DatabaseContext gets two DbSet properties; auto-discovery picks up the
configuration classes.
- Generated migrations for MySQL, Postgres, and SQLite create matching schemas;
EF truncates FK and index names on providers with 64-char identifier limits,
which is consistent with the rest of the codebase.
* [PM-36949] Wire up DI and add tests for plan migration cohort repositories
Register both Dapper and EF Core repositories in their respective service
collection extensions, following the existing AddSingleton convention in
these files.
Add tests:
- MigrationPathIdsSnapshotTests guards the immortal byte IDs that downstream
code pins on. The class- and method-level comments document why these
values can never be renumbered.
- MigrationPathTests covers the FromId round-trip and the null-on-unknown
behavior the registry promises to callers.
- OrganizationPlanMigrationCohortRepositoryTests exercises CRUD, the UNIQUE
Name constraint, and verifies that ReplaceAsync ignores CreatedAt
mutations (per the accept-but-don't-assign Update SP).
- OrganizationPlanMigrationCohortAssignmentRepositoryTests exercises CRUD,
GetByOrganizationIdAsync, the UNIQUE OrganizationId constraint,
cascade-delete from both Organization and Cohort, and verifies that
ReplaceAsync ignores OrganizationId, CohortId, and CreatedAt mutations.
* [PM-36949] Use PlanType and MigrationPathId enums on MigrationPath
Replace the byte Id with a byte-backed MigrationPathId enum and replace
the string FromPlan/ToPlan fields with PlanType. Persistence is
unchanged -- EF normalises enum-backed properties to their underlying
type in the model snapshot, and Dapper handles enum-to-byte parameter
mapping automatically.
* [PM-36949] Add *.lscache to .gitignore
* [PM-36949] fix: Override ReplaceAsync on EF cohort repositories for immutability parity
The Dapper _Update SPs accept-but-don't-assign certain columns (CreatedAt
on cohort; OrganizationId, CohortId, and CreatedAt on assignment), but
the base EF Repository<T,TEntity,Guid>.ReplaceAsync uses SetValues which
writes every scalar. Override on both repos and mark the immutable
properties as IsModified = false so MySQL/Postgres/Sqlite match MSSQL
behavior. Mirrors the existing DeviceRepository.ReplaceAsync pattern.
* [PM-36949] fix: Bound cohort string columns and widen Name to 255 chars
Add [MaxLength] attributes to the three cohort string properties so the
EF providers (MySQL/Postgres/Sqlite) enforce the same limits as MSSQL,
where the columns were already NVARCHAR-capped. Widen Name from 64 to
255 chars across MSSQL DDL, both _Create/_Update SP signatures, the
Migrator script, the entity, and all three regenerated EF migrations.
Coupon codes stay at 64 (Stripe IDs are short).
* [PM-36949] test: Lock FromPlan and ToPlan per MigrationPath
The snapshot test class doc says "byte N means a specific FromPlan ->
ToPlan transition forever", but only the byte value was being asserted.
A silent refactor of the registry's PlanType references would not have
been caught. Add per-path FromPlan/ToPlan assertions to close the gap.
* [PM-36949] chore: Apply dotnet format
* [PM-36949] test: Use LaxDateTimeComparer for round-tripped DateTimes
Postgres timestamp and MySQL datetime(6) store microsecond precision (6
fractional digits), but .NET DateTime is 100ns ticks (7 digits). Exact
Assert.Equal fails by a single tick on round-trip. Switch the three
DateTime comparisons in ReplaceAsync_UpdatesMutableColumns_AndIgnoresImmutableOnes
to LaxDateTimeComparer.Default -- the same 2ms-tolerance comparer used
by SendRepositoryTests and InstallationRepositoryTests for the same
precision issue.
* [PM-36949] refactor: Rename DateTime columns to Date suffix per naming convention
Addresses PR review feedback. Also tightens DATETIME2(7) / NVARCHAR(N) spacing
per the SQL style guide, drops *.lscache from .gitignore (handled by #7648),
and regenerates EF migrations for MySQL, Postgres, and SQLite.
* Apply dotnet format to regenerated migrations
* [PM-36949] chore: Align NULL/NOT NULL columns in cohort tables
* PM-37166 - Add ClientVersion to Device entity and repository contract
* PM-37166 - Add ClientVersion SQL schema and refactor bump stored procedures
* PM-37166 - Implement combined bump in repositories and add EF migrations
EF snapshot regeneration also absorbs Collection / CollectionGroup /
CollectionUser namespace moves (Bit.Infrastructure.EntityFramework.Models
-> Bit.Infrastructure.EntityFramework.AdminConsole.Models) that were left
un-regenerated by PR #7523 (PM-35489). Namespace-only, no SQL impact;
flagged with the AC team for awareness.
* PM-37166 - Replace DeviceLastActivityCacheService with DeviceDataCacheService
* PM-37166 - Replace BumpDeviceLastActivityDateCommand with BumpDeviceDataCommand
* PM-37166 - Pass ClientVersion through identity request validators
* PM-37166 - Align migration script with SQL style guide
Refresh Device_ReadBy* sprocs after DeviceView change so their cached
schema picks up ClientVersion, swap retired-sproc drops to
DROP PROCEDURE IF EXISTS, and tighten the ALTER TABLE indent in step 1.
* PM-37166 - Rename BumpData to UpdateLastActivity across device write pathway
The "BumpData" naming was vague — "data" named a category, not the thing
being written. Rename to "UpdateLastActivity" everywhere: SP, repositories,
command, cache, validators, tests. "Last activity" names the event of the
device's most recent appearance; LastActivityDate (when) and ClientVersion
(what was running) are facts we observed about that event. ClientVersion is
treated as a property of the activity event rather than an independent value,
so future last-observed properties (last IP, OS, etc.) slot in without renaming.
The SQL layer uses Update* per architect guidance on bitwarden/server#7302;
the Bump* SPs in this codebase are legacy and not being extended. The
extensibility note lives on IUpdateDeviceLastActivityCommand with short
pointers from the SP, repo, and cache.
Cache key prefix changes from device:data: to device:last-activity: — safe
because the cache is only a write-suppression optimization (SP guards ensure
correctness) and entries TTL out within 24h.
Migration renamed to 2026-05-14 to reflect the rewrite.
* PM-37166 - Add UTF-8 BOM to device last activity cache files
Aligns file encoding with the repo's .editorconfig (charset = utf-8-bom for .cs) so dotnet format --verify-no-changes passes.
* PM-37166 - Compare LastActivityDate at second precision in device creation test
GetManyByUserIdWithDeviceAuth_ReturnsLastActivityDate_ForNewDeviceAsync was
flaking on SqlServer: Dapper binds DateTime params as legacy `datetime`
(~3.33ms granularity), so the entity initializer's UtcNow can be rounded a
few ms earlier than the in-memory `beforeCreation` capture, making a strict
>= comparison occasionally false. Truncate both sides to the second to
absorb that drift while still rejecting stale or defaulted values.
* PM-37166 - Rename Device.ClientVersion EF migration
Renames migration class/files from AddDeviceClientVersionRefactorDeviceDataBump
to AddDeviceClientVersion to drop stale "Bump" terminology and the misleading
"RefactorDeviceData" prefix. The EF migration only adds the ClientVersion
column; the BumpData -> UpdateLastActivity SP refactor lives in MSSQL .sql
files and has no EF representation.
* PM-37166 - Document null-is-no-op semantics for ClientVersion on IUpdateDeviceLastActivityCommand
Tighten the interface-level summary and add a <param> note clarifying that
a null clientVersion is treated as "no opinion" and will not clear an
existing stored value.
* PM-37166 - Regenerate Device.ClientVersion EF migration on post-#7634 baseline
PR #7634 merged AddLastApiKeyRotationDateToUserTable into main while this
branch was open. The prior AddDeviceClientVersion migration's frozen model
snapshot (its .Designer.cs) was generated before that PR landed, so it did
not include User.LastApiKeyRotationDate. Applying migrations incrementally
against that stale snapshot would produce an inconsistent model graph.
Regenerated AddDeviceClientVersion on top of the merged-from-main baseline
so the new .Designer.cs files include both columns. The migration body
itself still only adds Device.ClientVersion; the top-level
DatabaseContextModelSnapshot.cs files were already correct from git's
three-way merge.
New timestamps (20260514192xxx) come after the User migration
(20260514011xxx), preserving migration order.
* PM-37166 - util/Migrator/DbScripts/2026-05-14_00_AddDeviceClientVersionAndUpdateLastActivitySp.sql - fix wrong comment
* PM-37166 - Bump Device.ClientVersion column width from 20 to 43
43 is the upper bound of Version.ToString() for any input parseable by
Version.TryParse — four Int32 components (Int32.MaxValue = 10 digits)
joined by 3 dots. Sizing to the type's mathematical max prevents
SQL Server error 8152 on malformed/hostile Bitwarden-Client-Version
headers without paying the cost of normalization at the call sites.
Real Bitwarden CalVer (YYYY.M.B) remains well within bounds at ~9 chars.
- Device.cs [MaxLength] + entity doc comment
- SSDT table + 4 stored procedures
- Cloud migration ALTER TABLE + SP parameters
- EF migrations regenerated for MySQL / Postgres / SQLite
* PM-37166 - Defer dropping old single-column UpdateLastActivityDate SPs to follow-up
Server and DB deploys are decoupled, so dropping the old SPs in the same migration that
introduces the new combined ones would break server rollback. Per discussion on PR #7632:
- Remove DROP PROCEDURE statements from the migration; replace with a note explaining the deferral.
- Restore the old Device_UpdateLastActivityDate{ById,ByIdentifierUserId}.sql files in src/Sql/dbo
so the SSDT source-of-truth stays aligned with deployed schema (EDD).
A follow-up ticket will drop the old SPs and delete the .sql files together once we're
confident no deployed server version still calls them.
* PM-37166 - Pass @LastActivityDate into Device_UpdateLastActivity SPs
Bitwarden convention is to compute timestamps in the application layer
and pass them as DATETIME2(7) params, not call GETUTCDATE() inside SPs.
Dapper repo now computes DateTime.UtcNow locally (matching the EF repo
and UserRepository.cs precedent) and passes LastActivityDate through.
* PM-37165 - Add LastApiKeyRotationDate column to User
Adds a nullable DATETIME2(7) LastApiKeyRotationDate column on the User
table alongside the other Last*Date audit columns. Covers the MSSQL
table, view, User_Create / User_Update stored procedures (new optional
parameter, EDD-safe with default NULL), the SSDT source-of-truth, and
EF migrations for MySql, Postgres, and Sqlite.
Repository round-trip integration tests verify that CreateAsync
defaults the column to NULL and ReplaceAsync persists it across all
four providers.
* PM-37165 - Add RotateUserApiKeyCommand under Auth/UserFeatures
Extracts user API key rotation out of UserService into a new CQS
command at src/Core/Auth/UserFeatures/UserApiKey/, mirroring the
existing decomposition pattern for other Auth user features. The
command generates a new 30-char ApiKey, bumps RevisionDate, sets
LastApiKeyRotationDate, and persists via IUserRepository.ReplaceAsync.
Adds the PM37165_RotateUserApiKeyCommand feature flag so the new path
can be rolled out behind a flag in a follow-up commit. Registers the
command via AddUserApiKeyCommands inside AddUserServices.
Unit tests verify the command assigns a fresh key, updates both
RevisionDate and LastApiKeyRotationDate to the same recent UTC value,
and calls ReplaceAsync exactly once.
* PM-37165 - Flag-gate rotate-api-key endpoint to new command
Wires AccountsController.RotateApiKey to dispatch between
IRotateUserApiKeyCommand (flag on) and the legacy
UserService.RotateApiKeyAsync (flag off) based on
PM37165_RotateUserApiKeyCommand. Both paths preserve the existing
auth and secret-verification guards, which run before the flag
branch.
Marks IUserService.RotateApiKeyAsync and its implementation [Obsolete]
pointing callers at IRotateUserApiKeyCommand, with TODOs tying their
removal to the flag cleanup. The body of the legacy method is
deliberately unchanged so it does NOT write LastApiKeyRotationDate
while the flag is off; that genuinely gates the new behavior so the
ramp is observable and reversible. The single remaining call site
(the controller fallback) is wrapped in #pragma warning disable
CS0618 so the attribute continues to flag any new callers.
Tests:
- AccountsControllerTests: dispatch tests for both flag states; the
auth and bad-secret guard tests are parameterized over flag state.
Pre-existing typo in two tests that called _sut.ApiKey() instead of
_sut.RotateApiKey() is fixed.
- UserServiceTests: regression test locks in the legacy non-write
behavior so it cannot drift before the flag is removed.
- AccountsControllerTest (integration): three endpoint tests cover
flag-off (LastApiKeyRotationDate stays NULL), flag-on (column is
populated), and bad-secret over both flag states (no rotation
occurs).
Each flag-state-specific test carries a TODO breadcrumb describing
the exact rename or deletion when the flag is cleaned up.
* PM-37165 - Tweak comment
* PM-37165 - Move LastApiKeyRotationDate to end of User schema
Append the new column to the end of User.sql, UserView.sql, the
matching CREATE OR ALTER VIEW in the migrator script, and the User
entity so SSDT mirrors what ALTER TABLE ADD produces in production.
* Add UseInviteLinks to Organization SQL schema and views
* Add Migrator scripts for UseInviteLinks column and data migration
* Add EF migrations for UseInviteLinks on Organization
* Wire UseInviteLinks through organization domain and repositories
* Add HasInviteLinks plan support and UseInviteLinks license handling
* Expose UseInviteLinks and HasInviteLinks on organization and plan API models
* Update tests for UseInviteLinks and invite-links plan feature
* Update migration script with missing update to Organization_ReadManyByIds
* Move UseInviteLinks column after ExemptFromBillingAutomation
* Bump date on migration scripts
* Add optional RevisionDate param to group sprocs
When provided, bump Group.RevisionDate on affected groups during
membership and collection-access changes. Defaults to NULL for
backward compatibility.
* Add migration for group RevisionDate bump
* Add revisionDate param to group repository methods
Update IGroupRepository and IOrganizationUserRepository interfaces
and their Dapper and Entity Framework implementations.
* Pass revisionDate through business logic to repos
Inject TimeProvider into commands, services, and controllers to
supply the timestamp when modifying group membership.
* Update unit tests for group revisionDate param
* Update and add integration tests for group revision
* Enhance IGroupRepository and IOrganizationUserRepository with detailed XML documentation
* Bump date on migration script
* Bump date on migration script
Deprecating V1 User Asymmetric Key information in favor of new V2 User Asymmetric Account Keys structure.
This PR adds support for the new AccountKeys structure while maintaining support for the legacy UserAsymmetricKey-based flow. Validation is updated to check either AccountKeys or UserAsymmetricKeys are updated. Tests include modeling for both scenarios.
* PM-4517 - Add LastActivityDate to Device entity, interfaces, DTOs, and response models
Adds the LastActivityDate nullable DateTime property to the Device entity,
IDeviceRepository interface (BumpLastActivityDateByIdAsync and
BumpLastActivityDateByIdentifierAsync), DeviceAuthDetails DTO,
DeviceResponseModel, DeviceAuthRequestResponseModel, and the
DevicesLastActivityDate feature flag key in Constants.
* PM-4517 - Add BumpDeviceLastActivityDateCommand with distributed cache guard
Adds IBumpDeviceLastActivityDateCommand and IDeviceLastActivityCacheService
interfaces with their implementations. The cache service uses the persistent
keyed IDistributedCache (Cosmos DB in cloud, SQL Server in self-hosted) with
a 48h TTL to guard against redundant DB writes within the same calendar day.
Moves device DI registration into a consolidated AddDeviceServices() extension.
* PM-4517 - Add LastActivityDate SQL schema, stored procedures, and MSSQL migration
Adds LastActivityDate DATETIME2 column to the Device table. Updates Device_Create
and Device_Update stored procedures. Adds Device_BumpLastActivityDateById and
Device_BumpLastActivityDateByIdentifier stored procedures with a CAST AS DATE
guard as a fallback against redundant writes when the application-layer cache
is unavailable.
* PM-4517 - Implement LastActivityDate repository methods and EF migrations
Implements BumpLastActivityDateByIdAsync and BumpLastActivityDateByIdentifierAsync
in both Dapper (via stored procedures) and EF (via ExecuteUpdateAsync with a
date-level guard). Adds EF migrations for Postgres, SQLite, and MySQL.
* PM-4517 - Bump device LastActivityDate on login and refresh token
Wires IBumpDeviceLastActivityDateCommand into BaseRequestValidator (login path,
keyed on device.Id) and CustomTokenRequestValidator (refresh token path, keyed
on device identifier from subject claims). Both call sites are feature-flagged
behind DevicesLastActivityDate.
* PM-4517 - Move AddDeviceServices() to AddBaseServices alongside IDeviceService
Device services are not user features — co-locating them with IDeviceService
in AddBaseServices is more cohesive than nesting them inside AddUserServices.
* PM-4517 - Swallow transient LastActivityDate bump failures to prevent auth disruption
* PM-4517 - Fix DeviceAuthDetails Dapper constructor parameter order to match LastActivityDate column position
* PM-4517 - Add edge case tests for BumpDeviceLastActivityForRefreshAsync guard conditions
* PM-4517 - Add tests for BumpLastActivityDate flag-disabled, null-device, and happy-path cases
* PM-4517 - Add PM-34091 cleanup TODOs to all DevicesLastActivityDate feature flag sites
* PM-4517 - Refine PM-34091 cleanup TODOs and add missing feature flag disabled test for refresh path
* PM-4517 - Remove redundant LastActivityDate shadow property from DeviceAuthDetails
* PM-4517 - Use CultureInfo.InvariantCulture in date string formatting for CA1305
* PM-4517 - Make _bumpDeviceLastActivityDateCommand protected in base to remove duplicate field in derived class
* PM-4517 - Scope device last activity cache key by userId to prevent cross-user collisions
The Device table's unique constraint is (UserId, Identifier), not Identifier alone,
so two users can share the same device identifier (e.g. account switching in a browser).
Scoping the cache key to device:last-activity:{userId}:{identifier} ensures that a cache
hit for one user never suppresses a DB write for another.
Also adds userId to BumpByIdAsync signature and reorders params to be consistent with
BumpByIdentifierAsync(string identifier, Guid userId).
* PM-4517 - Widen try-catch in TryBumpDeviceLastActivityForRefreshAsync and add happy-path test
Renames BumpDeviceLastActivityForRefreshAsync to TryBumpDeviceLastActivityForRefreshAsync
to signal the swallow-on-error intent. Moves the try-catch to wrap the entire method body,
including GetSubjectId() which can throw InvalidOperationException, so no exception can
escape and disrupt token refresh. Also moves the XML doc comment to RecordActivityForInstallation
where it belongs, and adds a happy-path test verifying BumpByIdentifierAsync is called
with the correct identifier and userId.
* PM-4517 - Capture DateTime.UtcNow once in EF bump methods to ensure consistent timestamp
Avoids a minor inconsistency where the WHERE filter and SET clause could evaluate
DateTime.UtcNow at slightly different moments, aligning behavior with the SQL stored
procedures which use a single @RevisionDate parameter.
* PM-4517 - Preserve LastActivityDate on Device_Update when null to prevent regressions
Device_Update previously overwrote LastActivityDate unconditionally, meaning any unrelated
device update (push token rotation, trust changes, deactivation) could silently regress a
recently-bumped value. COALESCE preserves the existing DB value when NULL is passed, while
still allowing callers to set it in the same write by passing a non-NULL value. The EF
ReplaceAsync override applies the same semantics via IsModified = false. Integration test
added to cover the preserve-on-null behaviour across all DB providers.
* PM-4517 - Add docs
* PM-4517 - Adjust docs
* PM-4517 - Add test coverage for BumpLastActivityDateByIdentifierAsync
* PM-4517 - Per PR feedback, add docs on IDeviceLastActivityCacheService
* PM-4517 - Per PR feedback, adjust IBumpDeviceLastActivityDateCommand.BumpById to be bump by device instead b/c it has all what we need.
* PM-4517 - Per PR feedback, add tech debt ticket.
* PM-4517 - Rename BumpByIdentifierAsync to BumpByIdentifierAndUserIdAsync across the board.
* PM-4517 - Per PR feedback, adjust stored proc names to meet SQL style requirements
* PM-4517 - Replace COALESCE with CASE in Device_Update to prevent stale non-null LastActivityDate overwrites
* PM-4517 - Add EF repository feature parity for replace logic + test to ensure we don't run into this again.
* PM-4517 - Fix DB migration order after main merge.
* PM-4517 - Regenerate EF DB migrations
* PM-4517 - actually regenerate EF DB migrations
* PM-4517 - Add LastActivityDate to Device_ReadActiveWithPendingAuthRequestsByUserId and integration tests
* Initial pass of revocation reason
* 2'nd pass, with tests, of revocation reason
* separate migration concerns, begin using new bulk sprocs
* remove old RevokeManyByIdAsync in favor of RevokeManyAsync
* fix migrations order
* Adjust other missing sprocs
* begrudgingly formats file
* No longer drop now-deprecated sprocs
* Add more views to refresh
* re-adds stored procs
* formatting from restoration
* Fix naming
* Modify sproc file name to match name
* PM-34130 - Fix DeviceAuthDetails constructor and stored procedure for EDD compliance
Replace positional 14-arg Dapper constructor with parameterless constructor and
property-setter mapping; rename AuthRequestCreatedAt to AuthRequestCreationDate;
convert IsTrusted to a computed property; update stored procedure to use explicit
column list instead of SELECT D.* for EDD-safe name-based Dapper mapping; add
migration script; expand integration tests for full field mapping, IsTrusted logic,
Unlock type eligibility, inactive device exclusion, and empty device list.
* PM-34130 - Fix EF constructor in DeviceAuthDetails to copy all Device fields
Copy UserId, PushToken, RevisionDate, EncryptedPrivateKey, and Active from
the source Device in the EF constructor. Previously these fields were omitted,
causing IsTrusted to always return false for EF-sourced results.
* PM-34130 - PR feedback resolution
* PM-34130 - Fix migration sort from main merge
* Fix UpdateCollectionCommand to set RevisionDate using TimeProvider and update corresponding tests. Adjust tests to verify correct RevisionDate assignment during collection updates.
* Enhance BulkAddCollectionAccessCommand to include revision date in access updates. Update ICollectionRepository and its implementations to accept revision date parameter. Modify stored procedure to update collection revision dates accordingly. Add tests to verify correct behavior of access creation and revision date updates.
* Update GroupRepository and stored procedures to bump RevisionDate for affected collections during group creation and updates. Enhance integration tests to verify that collection revision dates are correctly updated when groups are created or modified.
* Implement revision date updates for affected collections in OrganizationUserRepository and related stored procedures. Add integration tests to ensure revision dates are correctly bumped during organization user creation and updates.
* Update database migration script
* Update migration script summary
* Refactor OrganizationUserReplaceTests to create collection first
* Refactor stored procedures to use Common Table Expressions (CTEs) for updating RevisionDate of affected collections. This change improves readability and maintainability by consolidating the logic for identifying affected collections in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections procedures.
* Enhance OrganizationUser_CreateManyWithCollectionsAndGroups stored procedure to accept RevisionDate parameter for updating affected collections. Update OrganizationUserRepository to utilize the provided RevisionDate when available, ensuring accurate revision date management during organization user operations.
* Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to utilize temporary table for CollectionUser data insertion. This change improves performance and maintains consistency in updating RevisionDate for affected collections.
* Refactor OrganizationUserRepository to consistently use RevisionDate from created OrganizationUsers when updating affected collections. This change enhances the accuracy of revision date management across the repository.
* Refactor tests to ensure consistent handling of RevisionDate across Group and Collection repositories. Update assertions to compare RevisionDate directly, improving accuracy in revision date management during tests.
* Restore BOM in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections
* Refactor GroupRepository and OrganizationUserRepository to improve handling of RevisionDate. Updated collection filtering logic to use HashSet for efficiency and ensured that affected collections are filtered by OrganizationId, enhancing accuracy in revision date management.
* Bump migration script date
* Remove internal set from RevisionDate on Group and OrganizationUser
The Dapper repositories use a System.Text.Json serialize/deserialize
round-trip to build *WithCollections objects. System.Text.Json silently
skips properties with non-public setters, so RevisionDate was reverting
to DateTime.UtcNow instead of preserving the value set in C#.
* Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to improve the logic for updating RevisionDate. The update now uses INNER JOINs to ensure accurate filtering of collections based on OrganizationId and CollectionUser data, enhancing the precision of revision date management.
* Fix sprocs styling
* Added early return to OrganizationUserRepository.CreateManyAsync if the supplied parameter is empty
* Add feature flag for Organization Invite Links
* Add OrganizationInviteLink database entity
* Add OrganizationInviteLink table sql script and also OrganizationInviteLinkView that reads from it
* Add OrganizationInviteLink stored procedures for CRUD operations
* Add SQL migration script
* Add EF migrations
* Add EF configurations
* Add IOrganizationInviteLinkRepository and integration tests
* Add OrganizationInviteLinkRepository Dapper implementation
* refactor(tests): Update OrganizationInviteLinkRepositoryTests to use [Theory] attribute for test cases
feat: add MasterPasswordSalt to database responses and DTOs
- Add Dapper migration scripts and update SQL project
- Include MasterPasswordSalt in database response models and DTOs
- Add null coalescing to User entity for MasterPasswordSalt
- Update EF queries to return MasterPasswordSalt
- Rename migrations for consistency
- Add test coverage for affected repositories
- Update EmergencyAccessTakeOverResponseModel tests
* Add more efficient sproc to retrieve PolicyDetails
for a single user. This closely matches the existing sproc
used by PolicyService and should be performant enough
to be used in the login flow
* Maintain feature flag for this critical path
feat: add MasterPasswordSalt column to User table
- Add MasterPasswordSalt column to User table in both Dapper and EF implementations
- Update User stored procedures (Create, Update, UpdateMasterPassword) to handle salt column
- Add EF migrations and update UserView with dependent views
- Set MaxLength constraint on MasterPasswordSalt column
- Update UserRepository implementations to manage salt field
- Add comprehensive test coverage for salt handling and normalization
* PM-32517 initial migration commit
* pm-32517 fixing integration unit test
* PM-32517 removing .claude changes
* PM-32517 changing implementation of migration test
* PM-32517 adding type for ReportFile
* PM-32517 adding report file type
* PM-32517 changing unit tests
* PM-32517 adding new statement in migration script
* PM-32035 - EmergencyAccessService - fix interface docs, method docs, and tests to cover grantee / grantor deletion which is supported today.
* PM-32035 - EmergencyAccessService - mark existing delete as deprecated
* PM-32035 - EmergencyAccess readme docs - fix deletion docs
* PM-32035 - Add new EmergencyAccessDetails_ReadByUserIds stored proc
* PM-32035 - Add migration script for EmergencyAccessDetails_ReadByUserIds
* PM-32035 - Build out GetManyDetailsByUserIdsAsync in repository layer plus add tests
* PM-32035 - EmergencyAccessRepo - DeleteManyAsync - remove grantee revision bump as not necessary since no EA sync data exists + update tests
* PM-32035 - Fix incorrect nullability annotation on EmergencyAccessDetails.GrantorEmail. Both the SQL view and EF projection use a LEFT JOIN to the User table, meaning the value can be null if the grantor's account no longer exists. Changed to string? and removed the required modifier since the class is only ever materialized from database queries, never directly instantiated.
* PM-32035 - Refactor DeleteEmergencyAccess command to offer new DeleteAllByUserIdAsync and DeleteAllByUserIdsAsync methods. Need to build out DeleteByIdAndUserIdAsync with a new stored proc.
* PM-32035 - Build out IEmergencyAccessRepository.GetDetailsByIdAsync because we need such a method in order to meet the product requirements to send grantor email notifications for normal deletions in the future.
* PM-32035 - Wire up DeleteEmergencyAccessCommand.DeleteByIdAndUserIdAsync to use new repository method emergencyAccessRepository.GetDetailsByIdAsync so we can send notifications. Now, it is full replacement for the existing emergency access service deletion method + has the new notification functionaliy requested.
* PM-32035 - Add more test coverage for DeleteByIdAndUserIdAsync
* PM-32035 - Fix missing GranteeAvatarColor and GrantorAvatarColor projections in EmergencyAccessDetailsViewQuery. The EF view query omitted both avatar color fields from its Select projection, causing the integration tests to fail on all non-SqlServer databases (MySql, Postgres, Sqlite) where EF is used instead of Dapper.
* PM-32035 - Rename migration after main merge revealed collision
* PM-32035 - Rename migration script
* PM-32035 - PR feedback - add ticket + todos to deprecated delete async method.
* PM-32035 - DeleteEmergencyAccessCommand - add logs if we don't have user data required to send email notifications.
* PM-32035 - PR Feedback - rename EmergencyAccessDetails_ReadByUserIds to EmergencyAccessDetails_ReadManyByUserIds
Purpose: UseMyItems is a new organization ability / plan flag
which is automatically enabled where UsePolicies is enabled,
but can be selectively disabled to disable My Items creation
when the Organization Data Ownership policy is turned on.
- new organization table column with all sprocs and views updated
- data migration to enable the feature for all organizations that already use policies (replicating existing behaviour)
- data and api models updated
- added to organization license file so it can be preserved in self-hosted instances
- note that we don't have a plan feature defined for this yet, so it is set based on UsePolicies to match the migration logic. Billing Team have a ticket to add this
* User V2UpgradeToken for key rotation without logout
* reset old v2 upgrade token on manual key rotation
* sql migration fix
* missing table column
* missing view update
* tests for V2UpgradeToken clearing on manual key rotation
* V2 to V2 rotation causes logout. Updated wrapped key 1 to be a valid V2 encrypted string in tests.
* integration tests failures - increase assert recent for date time type from 2 to 5 seconds (usually for UpdatedAt assertions)
* repository test coverage
* migration script update
* new EF migration scripts
* broken EF migration scripts fixed
* refresh views due to User table alternation
* docs(billing): add design document for replacing SetupIntent cache
* docs(billing): add implementation plan for replacing SetupIntent cache
* feat(db): add gateway lookup stored procedures for Organization, Provider, and User
* feat(db): add gateway lookup indexes to Organization, Provider, and User table definitions
* chore(db): add SQL Server migration for gateway lookup indexes and stored procedures
* feat(repos): add gateway lookup methods to IOrganizationRepository and Dapper implementation
* feat(repos): add gateway lookup methods to IProviderRepository and Dapper implementation
* feat(repos): add gateway lookup methods to IUserRepository and Dapper implementation
* feat(repos): add EF OrganizationRepository gateway lookup methods and index configuration
* feat(repos): add EF ProviderRepository gateway lookup methods and index configuration
* feat(repos): add EF UserRepository gateway lookup methods and index configuration
* chore(db): add EF migrations for gateway lookup indexes
* refactor(billing): update SetupIntentSucceededHandler to use repository instead of cache
* refactor(billing): simplify StripeEventService by expanding customer on SetupIntent
* refactor(billing): query Stripe for SetupIntents by customer ID in GetPaymentMethodQuery
* refactor(billing): query Stripe for SetupIntents by customer ID in HasPaymentMethodQuery
* refactor(billing): update OrganizationBillingService to set customer on SetupIntent
* refactor(billing): update ProviderBillingService to set customer on SetupIntent and query by customer
* refactor(billing): update UpdatePaymentMethodCommand to set customer on SetupIntent
* refactor(billing): remove bank account support from CreatePremiumCloudHostedSubscriptionCommand
* refactor(billing): remove OrganizationBillingService.UpdatePaymentMethod dead code
* refactor(billing): remove ProviderBillingService.UpdatePaymentMethod
* refactor(billing): remove PremiumUserBillingService.UpdatePaymentMethod and UserService.ReplacePaymentMethodAsync
* refactor(billing): remove SubscriberService.UpdatePaymentSource and related dead code
* refactor(billing): update SubscriberService.GetPaymentSourceAsync to query Stripe by customer ID
Add Task 15a to plan - this was a missed requirement for updating
GetPaymentSourceAsync which still used the cache.
* refactor(billing): complete removal of PremiumUserBillingService.Finalize and UserService.SignUpPremiumAsync
* refactor(billing): remove ISetupIntentCache and SetupIntentDistributedCache
* chore: remove temporary planning documents
* chore: run dotnet format
* fix(billing): add MaxLength(50) to Provider gateway ID properties
* chore(db): add EF migrations for Provider gateway column lengths
* chore: run dotnet format
* chore: rename SQL migration for chronological order
* [PM-31684] Remove email hashing for send access
* [PM-31684] switching the order of migration files
* [PM-31684] adding more migrations
* [PM-31684] Removing anon access emails field and reusing emails field
* [PM-31684] cleanup before adding migrations back
* [PM-31684] restore original snapshots
* [PM-31684] restore original postgres snapshots
* [PM-31684] adding migrations
* [PM-31684] removing encryption attributes from emails request model
* [PM-31684] adding missing stored proc alters
* [PM-31684] Improved formatting for stored proc defs
* [PM-31684] adding necessary comment back
* [PM-31684] adding case-insensitive check on the server for send auth
* Begin migration to appropriately named sprocs
* Update method and parameter names
* Remove incorrect change
* Changes EF to match collection type comparison
* Adds integration test verifying excluded collections
* Changes EF to match collection type comparison
* Fix whitespacing
* Fix dedented if
* Implement the detail Subscription Discount Database Infrastructure
* Change string to string list
* fix lint error
* Create all missing database object definition files
* Regenerate EF migrations with Designer files
The previous migrations were missing .Designer.cs files. This commit:
- Removes the incomplete migration files
- Regenerates all three provider migrations (MySQL, Postgres, SQLite) with proper Designer files
- Updates DatabaseContextModelSnapshot.cs for each provider
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix failing database
* Resolve the lint warnings
* Resolve the database failure
* Fix the build Lint
* resolve the dbops reviews
* Add the default value
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: Add initial DeleteEmergencyContactCommand
* chore: remove nullable enable and add comments
* test: add tests for new delete command
* test: update tests to test IMailer was called.
* feat: add delete by GranteeId and allow for multiple grantors to be contacted.
* feat: add DeleteMany stored procedure for EmergencyAccess
* test: add database tests for new SP
* feat: commands use DeleteManyById for emergencyAccessDeletes
* claude: send one email per grantor instead of a bulk email to all grantors. Modified tests to validate.
* feat: change revision dates for confirmed grantees;
* feat: add AccountRevisionDate bump for grantee users in the confirmed status
* test: update integration test to validate only confirmed users are updated as well as proper deletion of emergency access
* models, entity, and stored procs updated to work with EmailHashes with migrations
* configure data protection for EmailHashes
* update SendAuthenticationQuery to use EmailHashes and perform validation
* respond to Claude's comments and update tests
* fix send.sql alignment
Co-authored-by: mkincaid-bw <mkincaid@bitwarden.com>
---------
Co-authored-by: Alex Dragovich <46065570+itsadrago@users.noreply.github.com>
Co-authored-by: mkincaid-bw <mkincaid@bitwarden.com>
* Exclude invited users from claimed domain checks.
These users should be excluded by the JOIN on
UserId, but it's a known issue that some invited
users have this FK set.
* Add sproc to create multiple default collections.
SqlBulkCopy implementation is overkill for most cases.
This provides a lighter weight sproc implementation for smaller
data sets.
* DRY up collection arrangement
* DRY up tests because bulk and non-bulk share same behavior
* use EF native AddRange instead of bulk insert, because
we expect smaller data sizes on self-host
We want to reduce the amount of business critical test data in the company. One way of doing that is to generate test data on demand prior to client side testing.
Clients will request a scene to be set up with a JSON body set of options, specific to a given scene. Successful seed requests will be responded to with a mangleMap which maps magic strings present in the request to the mangled, non-colliding versions inserted into the database. This way, the server is solely responsible for understanding uniqueness requirements in the database. scenes also are able to return custom data, depending on the scene. For example, user creation would benefit from a return value of the userId for further test setup on the client side.
Clients will indicate they are running tests by including a unique header, x-play-id which specifies a unique testing context. The server uses this PlayId as the seed for any mangling that occurs. This allows the client to decide it will reuse a given PlayId if the test context builds on top of previously executed tests. When a given context is no longer needed, the API user will delete all test data associated with the PlayId by calling a delete endpoint.
---------
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
* V2 prep, rename existing SSO JIT MP command to V1
* set initial master password for account registraton V2
* later removel docs
* TDE MP onboarding split
* revert separate TDE onboarding controller api
* Server side hash of the user master password hash
* use `ValidationResult` instead for validation errors
* unit test coverage
* integration test coverage
* update sql migration script date
* revert validate password change
* better requests validation
* explicit error message when org sso identifier invalid
* more unit test coverage
* renamed onboarding to set, hash naming clarifications
* update db sql script, formatting
* use raw json as request instead of request models for integration test
* v1 integration test coverage
* change of name