Skip to content

Auth/Innovation/PM-4517 - Device Management - Add Last Activity Date#7302

Merged
JaredSnider-Bitwarden merged 46 commits intomainfrom
auth/pm-4517/devices-add-last-activity-date
Apr 16, 2026
Merged

Auth/Innovation/PM-4517 - Device Management - Add Last Activity Date#7302
JaredSnider-Bitwarden merged 46 commits intomainfrom
auth/pm-4517/devices-add-last-activity-date

Conversation

@JaredSnider-Bitwarden
Copy link
Copy Markdown
Contributor

@JaredSnider-Bitwarden JaredSnider-Bitwarden commented Mar 24, 2026

🎟️ Tracking

Epic: https://bitwarden.atlassian.net/browse/PM-4516
Individual Tasks:

Clients PR: bitwarden/clients#19784

📔 Objective

We want to add a new last activity date column on our client's device management page, and we want to bump that date on login and successful refresh token usage. However, due to the volume of requests, we have to use a cache and SQL to guard against excessive writes to the DB.

File Changes by Layer

Database
  • src/Sql/dbo/Tables/Device.sql — Added LastActivityDate DATETIME2(7) NULL column
  • src/Sql/dbo/Stored Procedures/Device_Create.sql — Added @LastActivityDate param (default NULL for backwards compatibility)
  • src/Sql/dbo/Stored Procedures/Device_Update.sql — Added @LastActivityDate param; uses a CASE expression to ensure LastActivityDate only moves forward — guards against both NULL passthrough and stale non-null overwrites from concurrent requests
  • src/Sql/dbo/Stored Procedures/Device_UpdateLastActivityDateById.sql — New SP — updates LastActivityDate by device ID, at most once per calendar day
  • src/Sql/dbo/Stored Procedures/Device_UpdateLastActivityDateByIdentifierUserId.sql — New SP — updates LastActivityDate by (UserId, Identifier), at most once per calendar day
  • src/Sql/dbo/Auth/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql — Updated to select LastActivityDate in result set
  • util/Migrator/DbScripts/2026-04-14_00_AddDeviceLastActivityDate.sql — SQL Server migration (idempotent)
  • util/{MySql,Postgres,Sqlite}Migrations/ — EF migrations for MySQL, PostgreSQL, SQLite
Entity & Repository
  • src/Core/Entities/Device.cs — Added LastActivityDate property
  • src/Core/Repositories/IDeviceRepository.cs — Added BumpLastActivityDateByIdAsync and BumpLastActivityDateByIdentifierAndUserIdAsync
  • src/Infrastructure.Dapper/Repositories/DeviceRepository.cs — Implemented both bump methods (Dapper/SQL Server)
  • src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs — Implemented both bump methods (EF); added ReplaceAsync override to preserve LastActivityDate when null
  • test/Infrastructure.IntegrationTest/.../DeviceRepositoryTests.cs — Integration tests: ReplaceAsync null-preservation, BumpLastActivityDateByIdentifierAndUserIdAsync happy-path and userId scoping
Commands & Services
  • src/Core/Auth/UserFeatures/Devices/Interfaces/IBumpDeviceLastActivityDateCommand.cs — New interface — BumpAsync (login path) and BumpByIdentifierAndUserIdAsync (refresh token path)
  • src/Core/Auth/UserFeatures/Devices/Interfaces/IDeviceLastActivityCacheService.cs — New interface — distributed cache check/write to limit bump writes to once per device per day
  • src/Core/Auth/UserFeatures/Devices/BumpDeviceLastActivityDateCommand.cs — Implementation — checks cache before writing to DB
  • src/Core/Auth/UserFeatures/Devices/DeviceLastActivityCacheService.cs — Implementation — composite (userId, identifier) cache key scoped to the persistent distributed cache
  • src/Core/Auth/UserFeatures/Devices/DeviceServiceCollectionExtensions.cs — DI registration for new services
  • src/SharedWeb/Utilities/ServiceCollectionExtensions.cs — Wires AddDeviceServices() into base service registration
  • src/Core/Constants.cs — Added FeatureFlagKeys.DevicesLastActivityDate
  • test/Core.Test/.../BumpDeviceLastActivityDateCommandTests.cs — Unit tests for command (cache hit/miss, correct args)
  • test/Core.Test/.../DeviceLastActivityCacheServiceTests.cs — Unit tests for cache service (key scoping, date comparison)
Auth / Request Validators
  • src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs — Calls BumpAsync on successful login
  • src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs — Calls TryBumpDeviceLastActivityForRefreshAsync (swallows errors) on token refresh
  • src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs — Injects IBumpDeviceLastActivityDateCommand
  • src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs — Injects IBumpDeviceLastActivityDateCommand
  • test/Identity.Test/.../BaseRequestValidatorTests.cs — Updated for new BumpAsync signature; added happy-path assertion
  • test/Identity.Test/.../CustomTokenRequestValidatorTests.cs — Tests for TryBumpDeviceLastActivityForRefreshAsync including happy-path and error-swallowing
  • test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs — Updated to inject IBumpDeviceLastActivityDateCommand
Response Models
  • src/Api/Models/Response/DeviceResponseModel.cs — Exposes LastActivityDate
  • src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs — Exposes LastActivityDate
  • src/Core/Auth/Models/Data/DeviceAuthDetails.cs — Includes LastActivityDate in device auth details query

…d 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.
…e 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.
…QL 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.
…ions

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.
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.
@JaredSnider-Bitwarden JaredSnider-Bitwarden added the ai-review-vnext Request a Claude code review using the vNext workflow label Mar 24, 2026
…ceService

Device services are not user features — co-locating them with IDeviceService
in AddBaseServices is more cohesive than nesting them inside AddUserServices.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

Logo
Checkmarx One – Scan Summary & Details9d181955-712a-4553-a138-7f09b0b9f0ca


New Issues (4) Checkmarx found the following issues in this Pull Request
# Severity Issue Source File / Package Checkmarx Insight
1 HIGH Path_Traversal src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs: 56
detailsMethod at line 56 of /src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs gets dynamic data from the model element. This ...
Attack Vector
2 MEDIUM CSRF src/Api/Vault/Controllers/CiphersController.cs: 1558
detailsMethod at line 1558 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
Attack Vector
3 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
4 MEDIUM CSRF src/Api/Vault/Controllers/CiphersController.cs: 1385
detailsMethod at line 1385 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
Attack Vector

Fixed Issues (24) Great job! The following issues were fixed in this Pull Request
Severity Issue Source File / Package
CRITICAL CVE-2026-4800 Npm-lodash-4.17.21
HIGH CVE-2022-37620 Npm-html-minifier-4.0.0
HIGH CVE-2025-64756 Npm-glob-10.4.5
HIGH CVE-2026-26996 Npm-minimatch-9.0.5
HIGH CVE-2026-26996 Npm-minimatch-3.1.2
HIGH CVE-2026-26996 Npm-minimatch-9.0.1
HIGH CVE-2026-27903 Npm-minimatch-9.0.1
HIGH CVE-2026-27903 Npm-minimatch-3.1.2
HIGH CVE-2026-27903 Npm-minimatch-9.0.5
HIGH CVE-2026-27904 Npm-minimatch-9.0.5
HIGH CVE-2026-27904 Npm-minimatch-3.1.2
HIGH CVE-2026-27904 Npm-minimatch-9.0.1
HIGH CVE-2026-29063 Npm-immutable-5.1.3
HIGH CVE-2026-32933 Nuget-AutoMapper-12.0.1
HIGH CVE-2026-33671 Npm-picomatch-2.3.1
HIGH CVE-2026-34043 Npm-serialize-javascript-6.0.2
HIGH Cxf5fb15b0-6576 Npm-serialize-javascript-6.0.2
MEDIUM CSRF src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 145
MEDIUM CSRF src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 97
MEDIUM CSRF src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs: 173
MEDIUM CSRF src/Api/Vault/Controllers/CiphersController.cs: 287
MEDIUM CVE-2025-13465 Npm-lodash-4.17.21
MEDIUM CVE-2025-67898 Npm-mjml-parser-xml-4.15.3
LOW CVE-2025-69873 Npm-ajv-8.17.1

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed 42 changed files adding a LastActivityDate column to the Device table, bumped on login and token refresh. The implementation uses a three-layer defense-in-depth strategy: distributed cache limits most DB calls, SQL stored procedure date guards handle cache misses, and the EF ReplaceAsync override prevents stale/null overwrites from concurrent requests. Test coverage is comprehensive across unit, integration, and validator layers.

Code Review Details

No issues found. The implementation is clean and well-documented across all layers — stored procedures, EF repositories, cache service, command, and validator integrations are consistent and correct. Error handling appropriately swallows bump failures with warning logs to avoid impacting login/refresh flows. The 48h cache TTL with date-string comparison correctly handles the edge case of bumps near midnight.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 97.35099% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.27%. Comparing base (0b942b8) to head (90fa176).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/Api/Models/Response/DeviceResponseModel.cs 0.00% 2 Missing ⚠️
...r/RequestValidators/CustomTokenRequestValidator.cs 95.65% 0 Missing and 1 partial ⚠️
...e.EntityFramework/Repositories/DeviceRepository.cs 97.14% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7302      +/-   ##
==========================================
+ Coverage   58.75%   63.27%   +4.51%     
==========================================
  Files        2071     2074       +3     
  Lines       91248    91391     +143     
  Branches     8129     8140      +11     
==========================================
+ Hits        53613    57825    +4212     
+ Misses      35721    31565    -4156     
- Partials     1914     2001      +87     

☔ 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.

Comment thread src/Core/Auth/Models/Data/DeviceAuthDetails.cs Outdated
Comment thread src/Core/Auth/UserFeatures/Devices/DeviceLastActivityCacheService.cs Outdated
JaredSnider-Bitwarden and others added 8 commits March 24, 2026 21:02
…ross-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).
… 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.
…onsistent 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.
…vent 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.
@JaredSnider-Bitwarden JaredSnider-Bitwarden removed the ai-review-vnext Request a Claude code review using the vNext workflow label Mar 25, 2026
enmande
enmande previously approved these changes Apr 1, 2026
mkincaid-bw
mkincaid-bw previously approved these changes Apr 1, 2026
Copy link
Copy Markdown
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

LGTM

addisonbeck
addisonbeck previously approved these changes Apr 3, 2026
Comment thread src/Core/Auth/Models/Data/DeviceAuthDetails.cs Outdated
Comment thread src/Core/Entities/Device.cs
…add-last-activity-date + DeviceAuthDetails merge conflict fix
Copy link
Copy Markdown
Contributor

@dereknance dereknance left a comment

Choose a reason for hiding this comment

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

Platform ✅

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

LGTM

@JaredSnider-Bitwarden JaredSnider-Bitwarden merged commit 28902ac into main Apr 16, 2026
55 of 58 checks passed
@JaredSnider-Bitwarden JaredSnider-Bitwarden deleted the auth/pm-4517/devices-add-last-activity-date branch April 16, 2026 15:45
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.

6 participants