Skip to content

Stamp the account e-mail into the session at sign-in - #14

Merged
fupelaqu merged 2 commits into
mainfrom
feature/session-account-email
Aug 22, 2026
Merged

Stamp the account e-mail into the session at sign-in#14
fupelaqu merged 2 commits into
mainfrom
feature/session-account-email

Conversation

@fupelaqu

Copy link
Copy Markdown
Contributor

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.admin already carries for an authorization decision.

No new session API. SessionDataDecorator already exposes a generic +(kv), and every one of these sites already uses it one line below to stamp the profile:

account.currentProfile match {
  case Some(profile) => session += (session.profileKey, profile.name)
  case _             =>
}
account.email match {                                            // new
  case Some(email) => session += (AccountSettings.SessionEmailKey, email)
  case _           =>
}

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.email is an Option — 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:

  • the e-mail is present in the session after sign-in;
  • it is absent for a gsm-only account. That second one is not decoration: it is what rules out an unconditional getOrElse("") stamp, which would otherwise pass the first test.

testkit/test480 passed. Cross-compiles 2.12 + 2.13; scalafmtCheck / test:scalafmtCheck clean.

One trap worth recording: signIn opens with its own signOut(), so a trailing signOut() 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, returns None, 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).

fupelaqu and others added 2 commits August 22, 2026 20:10
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

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.91%. Comparing base (09ab5ad) to head (527a763).

Files with missing lines Patch % Lines
...work/account/service/AccountServiceEndpoints.scala 83.33% 1 Missing ⚠️
...etwork/account/service/OAuthServiceEndpoints.scala 66.66% 1 Missing ⚠️
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     
Flag Coverage Δ
unittests 74.91% <87.50%> (+0.38%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@fupelaqu
fupelaqu merged commit 6ad26e6 into main Aug 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant