Skip to content

PM-33964 - Resolve defect in premium user licensing#7788

Open
theMickster wants to merge 8 commits into
mainfrom
seeder/defect-premium-user-licensing
Open

PM-33964 - Resolve defect in premium user licensing#7788
theMickster wants to merge 8 commits into
mainfrom
seeder/defect-premium-user-licensing

Conversation

@theMickster

@theMickster theMickster commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

PM-33964

📔 Objective

The Seeder didn't take into account the self-hosted licensing file flow. We found that after first login, a premium user was stripped of that configuration in LicensingService.ValidateUserPremiumAsync. We found that SingleUserScene never set MaxStorageGb or PremiumExpirationDate for premium users, breaking attachments and File Sends.

I leveraged the logic found in src/Core/Billing/Services/Implementations/LicensingService.cs along with anywhere I found globalSettings.LicenseDirectory or similar. Please do let me know if I missed something.

Work Completed to Resolve the Defect

  • Registered LicensingService; added WritePremiumUserLicenseStep to the pipeline and WriteLicenseAsync to SingleUserScene (write a license file to LicenseDirectory after seeding)
  • License file writing is fail-soft and silently skips when signing cert is unavailable (self-hosted mode) or LicenseDirectory is not configured
  • Added step-ordering test confirming CreateIndividualUserStep runs before WritePremiumUserLicenseStep — ordering is a correctness invariant since the license step reads context.Owner
  • Validated end-to-end on local self-hosted dev: seeded premium user via SeederApi, confirmed license file written to disk with valid signed JWT, logged into self-hosted web vault, confirmed Premium = 1 and PremiumExpirationDate unchanged post-login
  • Negative test confirmed: free users get no license file

Follow-up 🚧

🚨 Follow-up Required — Cross-Database User Population

This PR fixes premium user licensing for self-hosted instances (license file written, MaxStorageGb set, PremiumExpirationDate set). However, it does not address the challenge of seeding users across both vault_dev and vault_dev_self_host.

Currently, end-to-end self-hosted login validation requires a manual SQL copy step after seeding via SeederApi or SeederUtility. A follow-up PR will close this gap by supporting direct seeding into the self-hosted database so the license file and user record land in the right place without manual intervention.

Notes 📝

  • I applied a full end-to-end test of the feature following the last commit ( 7d0b8ff ) just to be sure I didn't break things during code review refactoring.

@theMickster theMickster added the ai-review Request a Claude code review label Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR fixes a defect where the Seeder did not produce the artifacts required for self-hosted premium validation. It registers LicensingService conditionally in both SeederApi and SeederUtility, adds a new GenerateSelfHostUserLicenseStep to the pipeline, and updates SingleUserScene to write a signed user license file when premium is requested and LicenseDirectory is configured. The license-write path is fail-soft so that a misconfigured environment continues to seed non-premium data instead of crashing.

Code Review Details

No new findings.

All 14 prior review threads on this PR are resolved, including the three previously flagged correctness issues (eager LicensingService registration without prerequisites, missing TimeProvider for push, and the non-optional-at-DI-time ILicensingService parameter). The code path for writing premium user license files is correctly gated on globalSettings.SelfHosted, Installation.Id, and LicenseDirectory to avoid constructing LicensingService in environments where it would throw. The new CreateIndividualUser_ProducesTwoStepsInOrder test pins the step-ordering invariant called out in the PR description.

The remaining architectural discussion about cross-database user population (vault_dev vs vault_dev_self_host) in the unresolved general-comments thread is correctly scoped as a follow-up in the PR description rather than this change.

@theMickster theMickster requested a review from bnagawiecki June 9, 2026 10:33
Comment thread util/SeederApi/Startup.cs Outdated
Comment thread util/Seeder/Scenes/SingleUserScene.cs Fixed
Comment thread util/Seeder/Steps/WritePremiumUserLicenseStep.cs Fixed
Comment thread util/SeederUtility/Configuration/SeederServiceFactory.cs Fixed
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.15%. Comparing base (837a9dd) to head (1646e65).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7788      +/-   ##
==========================================
- Coverage   61.15%   61.15%   -0.01%     
==========================================
  Files        2175     2175              
  Lines       96784    96784              
  Branches     8730     8730              
==========================================
- Hits        59187    59186       -1     
- Misses      35487    35488       +1     
  Partials     2110     2110              

☔ View full report in Codecov by Harness.
📢 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 util/Seeder/Steps/WritePremiumUserLicenseStep.cs Outdated
Comment thread util/SeederApi/Startup.cs Outdated
Comment thread util/Seeder/Scenes/SingleUserScene.cs Fixed
Comment thread util/Seeder/Scenes/SingleUserScene.cs Fixed
Comment thread util/Seeder/Scenes/SingleUserScene.cs Fixed
Comment thread util/Seeder/Scenes/SingleUserScene.cs Fixed
Comment thread util/Seeder/Steps/WritePremiumUserLicenseStep.cs Fixed
Comment thread util/Seeder/Steps/WritePremiumUserLicenseStep.cs Fixed
theMickster and others added 2 commits June 9, 2026 13:30
…tch block'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tch block'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Comment thread util/Seeder/Scenes/SingleUserScene.cs Outdated
Comment thread util/Seeder/Scenes/SingleUserScene.cs Outdated

@MGibson1 MGibson1 left a comment

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.

nice working on self host licensing! I was under the impression that we commonly re-read and validate license files signatures, but I don't want to add the production license to QA self host instances. How are you getting around that? Is the signature not validated when performing actions?

@theMickster

Copy link
Copy Markdown
Contributor Author

nice working on self host licensing! I was under the impression that we commonly re-read and validate license files signatures, but I don't want to add the production license to QA self host instances. How are you getting around that? Is the signature not validated when performing actions?

Here's what I found when digging deeper (with help from Claude to find the right files & line numbers)

  • Signatures are always validated - no bypass. ValidateUserPremiumAsync (auth path: ProfileService.cs:62, UserClientProvider.cs:59; plus the scheduled jobs) → GetClaimsPrincipalFromLicense cryptographically validates the JWT. Bad sig → logout (LicensingService.cs:364-399).
  • We aren't creating unsigned licenses — they are signed with the dev key. Seeder runs cloud-mode (SelfHosted == false), so it signs with licensing_dev from the local workstation's licenseCertificatePath.
  • Dev self-host trusts it via the env gate — LicensingService.cs:91-95 trusts the dev cert whenever !IsProduction().
  • As we are aware (and I now understand much better now), the QA environment is the real challenge to consume this work. We need to a plan for a new dedicated QA licensing keypair.

We still need to address the Follow-up 🚧 section in the description of the PR in the future, but I don't think that blocks this work.

@theMickster theMickster requested a review from eliykat June 10, 2026 04:41
@MGibson1

Copy link
Copy Markdown
Member

I don't understand how that can be true. The use case does not have an associated vault_dev. We need to think about this in terms of our QA self host environments, which just have a single vault db, which happens to be self host.

@theMickster

Copy link
Copy Markdown
Contributor Author

I don't understand how that can be true. The use case does not have an associated vault_dev. We need to think about this in terms of our QA self host environments, which just have a single vault db, which happens to be self host.

lol... i wrote that comment long before our chat yesterday evening and you clarifying the reality that i clearly missed 🙃

Comment thread util/SeederApi/Startup.cs
// License infrastructure — only registered when configuration is sufficient to construct
// LicensingService without throwing. SingleUserScene accepts ILicensingService? and
// no-ops gracefully when the service is absent (e.g. self-hosted dev with no LicenseDirectory).
if (!globalSettings.SelfHosted || (globalSettings.Installation.Id != Guid.Empty && CoreHelpers.SettingHasValue(globalSettings.LicenseDirectory)))

@eliykat eliykat Jun 13, 2026

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.

So this only runs on a cloud environment, to set up the user and generate a license file for them, which you can then upload to a self-host environment. Is that right?

Wouldn't the more direct path be to run this logic on the self-host environment, so that you get the user + license file seeded where you actually want them and ready to use? Otherwise the flow is only semi-automated and still requires human intervention to actually initialize the account on self-host (which is meant to be the seeder's job).

That may pose similar problems for how to generate and sign the license file in a way that the self-host server accepts.... but you kind of already have that problem here (per discussions above). Even so, it may not be possible to generate the cert on the destination environment - but I just wanted to confirm my understanding of both the problem and the solution.

(Code changes otherwise LGTM.)

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants