Commit graph yavsc/src/Yavsc.Tests.Shared/TestTokenIssuer.cs
Author SHA1 Message Date
8c8ab70d38
REORG 2026-09-26 00:35:43 +01:00
a307885310
Blogs: list personal files under the user's login, not the sub claim
GET api/v1/fs passed User.GetUserId() (the "sub" claim — a GUID in
production) to GetUserFiles, so the listing looked under
{Blog}/{sub-GUID}. But the personal-storage tree is keyed by the
user's login everywhere else: EnsureDestinationDirectory writes under
{Blog}/{name-claim}, the download endpoint and avatar paths use
user.UserName. So once the upload path was fixed (previous commit),
files landed under {Blog}/{login} while the listing still looked under
{Blog}/{GUID} — an empty tree — hence the PostIt "Mes fichiers" page
listing nothing.

GetDir now resolves the user from the DB (as Post already does) and
lists under user.UserName. UploadedFile.OwnerId stays "sub", so
EnrichWithFileIds still queries by uid.

Tests:
- TestTokenIssuer.Issue gains an optional `name` param (defaults to
  subject, preserving existing behavior) so a test token can mirror
  production where sub (GUID) ≠ name (login).
- FileSystemApiTests.Get_fs_lists_files_stored_under_the_user_login
  decouples sub from the login, uploads under the login, and asserts
  GET /api/v1/fs returns the file (and the subject-named directory does
  NOT exist). Verified to fail when the controller uses uid for the
  listing.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:50:58 +01:00
d8c13c81d7
Server: wire ProfileService so access tokens carry the name claim
The custom ProfileService (Yavsc.Services.ProfileService) was never
registered with IdentityServer, so IS8 fell back to its default
profile service — which only emits claims listed in each API
resource's UserClaims (RequestedClaimTypes). "name" was never among
them, so every access token lacked "name" and GetUserName() resolved
to null on all bearer resource hosts (Blogs/Api): the MyFiles upload
500 and the unreachable User.Identity.Name. The class was dead code;
all prior tweaks to it (FullName→UserName) and to ServiceExtensions
(MapInboundClaims=false) were necessary but insufficient until it was
actually wired in.

- HostingExtensions: AddProfileService<ProfileService>() after
  AddAspNetIdentity<ApplicationUser>().
- ProfileService: emit name=user.UserName, email, roles for any user
  grant whose client has "profile" in AllowedScopes, independent of
  the caller (the old ParsedScopes gate only fired for the id-token
  caller, never the access-token caller).
- ServiceExtensions: JwtBearer NameClaimType=JwtClaimTypes.Name,
  MapInboundClaims=false (keep short claim names as-emitted).
- FileSystemHelpers: EnsureDestinationDirectory uses GetUserName()
  (the "name" claim) instead of User.Identity.Name for the per-user
  storage root.

Tests:
- TestTokenIssuer: stamp a "name" claim = subject so test tokens mirror
  production shape (fixes FileSystemApiTests upload 500).
- Remoting: PasswordToken_carries_name_claim_equal_to_login mints a
  real JWT via the IS token endpoint (password grant, openid profile)
  and asserts the access token carries name=login and GetUserName()
  resolves to it.
- WebServerFixture: AccessTokenType=0 (Jwt in IS8; 1=Reference, the
  inverse of Duende); allow openid+profile on the test client; re-seed
  the openid/profile identity resources in ReseedAuthTestData (a
  ResetAndMigrateDatabase wipes the shared in-memory store and previously
  left them gone → invalid_scope).
- BillingServiceTests: [Collection("Yavsc Server")] to serialize with
  the host build that mutates the static BillingService/Config
  registries (pre-existing parallel race that the new test's timing
  surfaced).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:42:54 +01:00
dee76bbeab test(blogs): real JwtBearer in fixture, drop X-Test-Role bypass
Wire the Blogs integration test host with a real AddJwtBearer
(HS256, IssuerSigningKey shared with the new TestTokenIssuer) and
the production BlogScope policy verbatim, instead of the
TestAuthPolicyProvider / AllowAllAuthorizationService /
NoopAuthHandler stack that short-circuited every authorization
check.

Why: BlogSpotService.Modify calls
IAuthorizationService.AuthorizeAsync(user, blog, EditPermission);
the previous AllowAllAuthorizationService stub made that a
no-op, so the tests could not exercise the real ownership chain
and any change in PermissionHandler would silently slip through.
The new test host registers the real PermissionHandler, so a PUT
that succeeds (204) is now proof that PermissionHandler.IsOwner
accepted the request — i.e. the JWT's sub matched the post's
AuthorId, end-to-end.

Notes for future-me:
  - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear() is
    called once on the first Issue() to keep the 'sub' claim
    literal; without it UserHelpers.GetUserId (which reads 'sub')
    gets ClaimTypes.NameIdentifier instead, returns null, and the
    owner check fails for every PUT. The companion
    options.MapInboundClaims = false on the validation pipeline
    keeps both sides in sync.
  - Production still uses AddYavscJwtBearer against the OIDC
    authority; the test-only HS256 path is local to the test
    process and never crosses a network boundary.

Coverage:
  - GetBlog_returns_401_when_no_token_is_provided — anonymous
    request, real policy fails closed.
  - PutBlog_with_valid_token_and_owner_returns_204_and_Get_
    reflects_update — POST then PUT then GET, all behind a real
    JWT, asserting 204 + list contains the updated title.

Packages added to Directory.Packages.props at 8.2.1 to match
what Microsoft.AspNetCore.Authentication.JwtBearer 10.0.9 already
transitively pulls in (no version drift).
2026-07-06 23:29:51 +01:00