Commit Graph

6 Commits

Author SHA1 Message Date
Jared Snider
eb9c8c9aa9 Auth/PM-37621 - Fix Device.LastActivityDate surfacing legacy NULL rows as DateTime.UtcNow (#7649)
* PM-37621 - Fix Device.LastActivityDate surfacing legacy NULL rows as DateTime.UtcNow

Dapper's deserializer skips the property setter when a nullable column is
DBNull, leaving the property at its CLR default. The field initializer
`public DateTime? LastActivityDate { get; internal set; } = DateTime.UtcNow`
poisoned that default, so rows whose LastActivityDate column was NULL (e.g.
devices created before the column existed) read back as the current time.

Drop the initializer, relax `internal set` to `set`, and stamp
LastActivityDate explicitly at the two creation call sites
(DeviceValidator.GetDeviceFromRequest and DeviceRequestModel.ToDevice). Adds
an integration regression test that creates a device with an explicit null
LastActivityDate and asserts the read path surfaces null. Augments
DeviceValidatorTests.GetDeviceFromRequest_RawDeviceInfoValid_ReturnsDevice
to lock in the creation-time stamp.

* PM-37621 - DeviceSeeder - creation should set LastActivityDate
2026-05-15 16:34:08 -04:00
Jared Snider
450159a1e2 Auth/PM-37166 - Devices - add client version (#7632)
* 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.
2026-05-14 20:22:13 -04:00
Jared Snider
28902acec8 Auth/Innovation/PM-4517 - Device Management - Add Last Activity Date (#7302)
* 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
2026-04-16 11:45:26 -04:00
Jared Snider
e0225f9e03 Auth/PM-34130 - Fix DeviceAuthDetails constructor and stored procedure for EDD compliance (#7416)
* 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
2026-04-13 15:50:18 -04:00
Patrick-Pimentel-Bitwarden
275f7ceb27 Auth/pm 17233/tests for multiple users on single device for web approvals (#5316)
* test(test-device-repository): [PM-17233] Add Test Case for Critical Bug Found in Device Repository - Added new test case for previously found bug.
2025-01-23 11:21:28 -05:00
Patrick-Pimentel-Bitwarden
cc96e35072 Auth/pm 2996/add auth request data to devices response model (#5152)
fix(auth): [PM-2996] Add Pending Auth Request Data to Devices Response
- New stored procedure to fetch the appropriate data.
- Updated devices controller to respond with the new data.
- Tests written at the controller and repository level.
Resolves PM-2996
2025-01-07 15:52:53 -05:00