| | | 1 | | using ArturRios.Data.Relational.Core.Interfaces; |
| | | 2 | | using ArturRios.Heimdall.Command.Input; |
| | | 3 | | using ArturRios.Heimdall.Command.Output; |
| | | 4 | | using ArturRios.Heimdall.Domain.Entities; |
| | | 5 | | using ArturRios.Heimdall.Shared.Messages; |
| | | 6 | | using ArturRios.Mediator.Command.Interfaces; |
| | | 7 | | using ArturRios.Output; |
| | | 8 | | using Microsoft.EntityFrameworkCore; |
| | | 9 | | |
| | | 10 | | namespace ArturRios.Heimdall.Command.Handlers; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Handles <see cref="GoogleSignOutCommand" /> (UC-26, FR-GO-18): confirms the caller's token |
| | | 14 | | /// still names a Google User in good standing and acknowledges the sign-out; every other caller |
| | | 15 | | /// is AF-26a. Nothing is written — see the remarks. |
| | | 16 | | /// </summary> |
| | | 17 | | /// <remarks> |
| | | 18 | | /// <para> |
| | | 19 | | /// UC-26 step 2 offers two ways to end a session — invalidate the token, or have the client |
| | | 20 | | /// discard it — and defers to "the configured token strategy". This project's is the stateless |
| | | 21 | | /// one UC-11 established: signed JWTs with an expiry (NFR-03), no refresh tokens and no |
| | | 22 | | /// revocation list. So the sign-out has nothing to revoke, and the endpoint's job is to |
| | | 23 | | /// confirm the caller is who UC-26 says may call it and answer the success that tells the |
| | | 24 | | /// client to drop the token. Adding a denylist here would be a token strategy no document |
| | | 25 | | /// chooses, and it would apply to every token the API issues, not just Google's. |
| | | 26 | | /// </para> |
| | | 27 | | /// <para> |
| | | 28 | | /// That leaves the lookup as the whole of the endpoint's substance, and it is not ceremony: |
| | | 29 | | /// authentication runs in <c>ClaimsOnly</c> mode — no database read per request — so a valid |
| | | 30 | | /// bearer token outlives the Google User it names once UC-28 logically deletes or UC-29 |
| | | 31 | | /// removes them, exactly as UC-15's does for a person. A caller in that position is refused, |
| | | 32 | | /// because UC-26's precondition is a live session issued by UC-25 and UC-25 itself would no |
| | | 33 | | /// longer authenticate them (AF-25d). |
| | | 34 | | /// </para> |
| | | 35 | | /// <para> |
| | | 36 | | /// Every refusal answers with <see cref="AuthMessages.GoogleAuthenticationFailed" />, the same |
| | | 37 | | /// message UC-25 uses, so the endpoint cannot be used to tell a deleted Google User apart from |
| | | 38 | | /// one that never existed. |
| | | 39 | | /// </para> |
| | | 40 | | /// </remarks> |
| | 10 | 41 | | public class GoogleSignOutCommandHandler(IAsyncReadOnlyRepository<GoogleUser, long> googleUserReader) |
| | | 42 | | : ICommandHandlerAsync<GoogleSignOutCommand, GoogleSignOutCommandOutput> |
| | | 43 | | { |
| | | 44 | | public async Task<DataOutput<GoogleSignOutCommandOutput?>> HandleAsync(GoogleSignOutCommand command, CancellationTok |
| | | 45 | | { |
| | 10 | 46 | | var output = DataOutput<GoogleSignOutCommandOutput?>.New; |
| | | 47 | | |
| | | 48 | | // UC-26 step 1 (AF-26a). The filter is part of the check rather than a separate branch: a |
| | | 49 | | // Google User that is missing and one that is logically deleted are refused alike, so the |
| | | 50 | | // answer distinguishes neither. The command carries no id of its own, so a caller can only |
| | | 51 | | // ever name themselves. |
| | 10 | 52 | | var signedIn = await googleUserReader.Query() |
| | 10 | 53 | | .AnyAsync(x => x.PublicId == command.ActingPersonId && !x.IsDeleted); |
| | | 54 | | |
| | 10 | 55 | | if (!signedIn) |
| | | 56 | | { |
| | 4 | 57 | | return output.WithError(AuthMessages.GoogleAuthenticationFailed); |
| | | 58 | | } |
| | | 59 | | |
| | | 60 | | // UC-26 steps 2 and 3: nothing to invalidate under the stateless strategy, so the success is |
| | | 61 | | // the instruction to discard the token. |
| | 6 | 62 | | return output |
| | 6 | 63 | | .WithData(new GoogleSignOutCommandOutput()) |
| | 6 | 64 | | .WithMessage(AuthMessages.GoogleSignOutSuccessful); |
| | 10 | 65 | | } |
| | | 66 | | } |