Stamp the account e-mail into the session at sign-in - #14
Merged
Conversation
Downstream services need to key trust decisions on the DOMAIN of the account's
e-mail. Today they cannot: SessionData carries only admin / profile / anonymous /
id / client_id, so a reader holding a session knows WHO the account is but not
what address it authenticated with. Their only alternative is an organisation or
profile contact address the requester can edit -- i.e. forge.
The licence server is the immediate consumer: it decides "one evaluation per
organisation" and refuses free-mail domains on that e-mail domain, and both rules
are bypassable while the address is user-editable.
Stamping it at sign-in puts the address behind the session's HMAC, which is the
same trust the `admin` flag already carries for authorization. No new session API
is needed: SessionDataDecorator already exposes a generic `+(kv)`, and every one
of these sites already uses it one line below to stamp the profile.
All SIX authentication paths stamp it -- AccountServiceEndpoints (x2),
AccountService (x2), OAuthServiceEndpoints, OAuthService. A missed path would not
fail; the reader would simply fall back to the editable address and be silently
wrong, so they are one change.
The key lives in AccountSettings as a constant, NOT as a config value: it is a
wire contract with those readers, so it must not be re-mappable per deployment.
`Account.email` is an Option -- an account registered by phone has none -- so the
key is ABSENT rather than empty in that case, letting a reader distinguish "no
address" from "an address we lost".
Tested in AccountRouteSpec, so it runs across all EIGHT concrete variants
(akka-http routes AND tapir endpoints, cookie AND header, one-off AND
refreshable): the e-mail is present after sign-in, and absent for a gsm-only
account -- the second assertion is what rules out an unconditional
`getOrElse("")` stamp. testkit/test: 480 passed. Cross-compiles 2.12 + 2.13.
See adr-evaluation-trusted-domain-source (elasticsql planning artifacts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries the sign-in session e-mail stamp. Downstream train: generic-account-api 0.8.8 -> softpayment -> softclient4es-license-server, which has no direct pin and resolves this transitively via Versions.softPayment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #14 +/- ##
==========================================
+ Coverage 74.52% 74.91% +0.38%
==========================================
Files 55 55
Lines 2595 2611 +16
Branches 385 419 +34
==========================================
+ Hits 1934 1956 +22
+ Misses 661 655 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A session tells a downstream reader who the account is (
id,admin,profile,anonymous,client_id) but not what address it authenticated with. Any service that needs to make a trust decision on the account's e-mail domain therefore has to fall back on a profile or organisation contact field the user can edit — i.e. forge.The licence server is the immediate consumer. It decides one evaluation per organisation and refuses free-mail domains on that domain, and today both rules are bypassable: edit the organisation contact, get a new domain. It can also be pointed at a domain the requester does not control.
What
Stamp the account e-mail into the session at sign-in, putting it behind the session's HMAC — the same trust
session.adminalready carries for an authorization decision.No new session API.
SessionDataDecoratoralready exposes a generic+(kv), and every one of these sites already uses it one line below to stamp the profile:All six authentication paths, as one change —
AccountServiceEndpoints×2,AccountService×2,OAuthServiceEndpoints,OAuthService. A missed path would not fail loudly: the reader would fall back to the editable address and be quietly wrong.The key is a constant in
AccountSettings, deliberately not a config value. It is a wire contract with those readers, so it must not be re-mappable per deployment.Account.emailis anOption— an account registered by phone has none — so the key is absent rather than empty, letting a reader distinguish no address from an address we lost.Tests
Added to
AccountRouteSpec, so they run across all eight concrete variants — akka-http routes and tapir endpoints, cookie and header, one-off and refreshable:getOrElse("")stamp, which would otherwise pass the first test.testkit/test— 480 passed. Cross-compiles 2.12 + 2.13;scalafmtCheck/test:scalafmtCheckclean.One trap worth recording:
signInopens with its ownsignOut(), so a trailingsignOut()in a test leaves stale headers and the next sign-in 403s.Release
ThisBuild / version := "0.8.8".Downstream train: generic-account-api 0.8.8 → softpayment → softclient4es-license-server, which has no direct pin and resolves this transitively via
Versions.softPayment.The licence-server read side is already written and is deliberately inert until this publishes:
session.get(...)compiles against 0.8.7, returnsNone, and falls back to the old source with a WARN. So there is no dependency-order trap and nothing breaks while the train runs — behaviour flips when this lands.Design recorded in
adr-evaluation-trusted-domain-source(elasticsql planning artifacts).