From 81b964e8c4fd6d9c159eb50e51f8e5f9358420e2 Mon Sep 17 00:00:00 2001 From: James Gunn Date: Fri, 22 Sep 2023 16:09:53 +0100 Subject: [PATCH] Add TRN verification elevation journey --- .../AuthenticationState.cs | 61 ++++++++++---- .../TeacherIdentity.AuthServer/Events/User.cs | 4 + .../Events/UserUpdatedEvent.cs | 4 +- .../IdentityLinkGenerator.cs | 4 + .../CoreSignInJourneyWithTrnLookup.cs | 29 ++++++- .../ElevateTrnVerificationLevelJourney.cs | 82 +++++++++++++++++++ .../Journeys/ServiceCollectionExtensions.cs | 2 + .../Journeys/SignInJourney.cs | 2 +- .../Journeys/SignInJourneyProvider.cs | 12 +-- .../Journeys/TrnLookupHelper.cs | 22 ++--- .../Journeys/UserHelper.cs | 69 ++++++++++++++-- .../Models/Mappings/UserMapping.cs | 1 + .../TeacherIdentity.AuthServer/Models/User.cs | 28 ++++++- .../Pages/SignIn/Complete.cshtml | 31 ++++++- .../Pages/SignIn/Complete.cshtml.cs | 6 ++ .../Pages/SignIn/Elevate/CheckAnswers.cshtml | 38 +++++++++ .../SignIn/Elevate/CheckAnswers.cshtml.cs | 34 ++++++++ .../Pages/SignIn/Elevate/Landing.cshtml | 34 ++++++++ .../Pages/SignIn/Elevate/Landing.cshtml.cs | 43 ++++++++++ .../Pages/SignIn/Register/EmailExists.cshtml | 2 +- .../SignIn/Register/NiNumberPage.cshtml.cs | 6 +- .../Pages/SignIn/Register/TrnPage.cshtml | 21 +++-- .../Pages/SignIn/Register/TrnPage.cshtml.cs | 16 +++- .../UserClaimHelper.cs | 7 +- .../Views/Home/Index.cshtml | 7 +- 25 files changed, 490 insertions(+), 75 deletions(-) create mode 100644 dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ElevateTrnVerificationLevelJourney.cs create mode 100644 dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml create mode 100644 dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml.cs create mode 100644 dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml create mode 100644 dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml.cs diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/AuthenticationState.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/AuthenticationState.cs index f60143b4c..78c701a64 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/AuthenticationState.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/AuthenticationState.cs @@ -130,6 +130,17 @@ public AuthenticationState( [JsonInclude] public bool HaveResumedCompletedJourney { get; private set; } + /// + /// Whether the signed in user requires elevating to the higher TrnVerificationLevel. + /// + /// + /// This should be set when the user is signed in and remain un-changed for the duration of the journey. + /// The property tracks whether elevation has completed, successfully or not. + /// + [JsonInclude] + public bool? RequiresTrnVerificationLevelElevation { get; private set; } + public bool? TrnVerificationElevationSuccessful { get; set; } + [JsonIgnore] public bool EmailAddressSet => EmailAddress is not null; [JsonIgnore] @@ -240,6 +251,8 @@ public void Reset(DateTime utcNow) InstitutionEmailChosen = default; PreferredName = default; HaveResumedCompletedJourney = default; + RequiresTrnVerificationLevelElevation = default; + TrnVerificationElevationSuccessful = default; } public void OnEmailSet(string email, bool isInstitutionEmail = false) @@ -359,6 +372,7 @@ public void OnTrnLookupCompletedAndUserRegistered(User user) FirstTimeSignInForEmail = true; Trn = user.Trn; TrnLookup = TrnLookupState.Complete; + RequiresTrnVerificationLevelElevation = false; UserType = user.UserType; StaffRoles = user.StaffRoles; TrnLookupStatus = user.TrnLookupStatus; @@ -594,6 +608,10 @@ public void OnSignedInUserProvided(User? user) LastName = user?.LastName; DateOfBirth = user?.DateOfBirth; Trn = user?.Trn; + RequiresTrnVerificationLevelElevation = + user is not null && TryGetOAuthState(out var oAuthState) && oAuthState.TrnMatchPolicy == TrnMatchPolicy.Strict ? + user.EffectiveVerificationLevel != TrnVerificationLevel.Medium : + null; HaveCompletedTrnLookup = user?.CompletedTrnLookup is not null; TrnLookup = user?.CompletedTrnLookup is not null ? TrnLookupState.Complete : TrnLookupState.None; UserType = user?.UserType; @@ -605,6 +623,7 @@ public void OnTrnTokenProvided(EnhancedTrnToken trnToken) { TrnToken = trnToken.TrnToken; Trn = trnToken.Trn; + RequiresTrnVerificationLevelElevation = false; TrnLookupStatus = AuthServer.TrnLookupStatus.Found; FirstName ??= trnToken.FirstName; MiddleName ??= trnToken.MiddleName; @@ -636,6 +655,11 @@ public void OnTrnLookupCompleted(FindTeachersResponseResult? findTeachersResult, Trn = trn; TrnLookupStatus = trnLookupStatus; + if (RequiresTrnVerificationLevelElevation == true) + { + TrnVerificationElevationSuccessful = Trn is not null; + } + if (findTeachersResult is not null && !string.IsNullOrEmpty(findTeachersResult.FirstName) && !string.IsNullOrEmpty(findTeachersResult.LastName)) { DqtFirstName = findTeachersResult.FirstName; @@ -652,21 +676,6 @@ public async Task SignIn(HttpContext httpContext) return await httpContext.SignInCookies(claims, resetIssued: true, AuthCookieLifetime); } - public enum HasPreviousNameOption - { - Yes, - No, - PreferNotToSay - } - - public enum TrnLookupState - { - None = 0, - Complete = 1, - ExistingTrnFound = 3, - EmailOfExistingAccountForTrnVerified = 4 - } - private void UpdateAuthenticationStateWithUserDetails(User user) { UserId = user.UserId; @@ -689,8 +698,30 @@ private void UpdateAuthenticationStateWithUserDetails(User user) if (HaveCompletedTrnLookup || Trn is not null) { TrnLookup = TrnLookupState.Complete; + RequiresTrnVerificationLevelElevation = + TryGetOAuthState(out var oAuthState) && oAuthState.TrnMatchPolicy == TrnMatchPolicy.Strict && + user.EffectiveVerificationLevel != TrnVerificationLevel.Medium; } } + else + { + RequiresTrnVerificationLevelElevation = false; + } + } + + public enum HasPreviousNameOption + { + Yes, + No, + PreferNotToSay + } + + public enum TrnLookupState + { + None = 0, + Complete = 1, + ExistingTrnFound = 3, + EmailOfExistingAccountForTrnVerified = 4 } } diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/User.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/User.cs index 1c6ed75b4..9238c72cd 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/User.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/User.cs @@ -17,6 +17,8 @@ public record User public required TrnAssociationSource? TrnAssociationSource { get; init; } public required string[] StaffRoles { get; init; } = Array.Empty(); public required TrnLookupStatus? TrnLookupStatus { get; init; } + public required TrnVerificationLevel? TrnVerificationLevel { get; init; } + public required string? NationalInsuranceNumber { get; init; } public static User FromModel(Models.User user) => new() { @@ -29,8 +31,10 @@ public record User StaffRoles = user.StaffRoles, Trn = user.Trn, MobileNumber = user.MobileNumber, + NationalInsuranceNumber = user.NationalInsuranceNumber, TrnAssociationSource = user.TrnAssociationSource, TrnLookupStatus = user.TrnLookupStatus, + TrnVerificationLevel = user.TrnVerificationLevel, UserId = user.UserId, UserType = user.UserType }; diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/UserUpdatedEvent.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/UserUpdatedEvent.cs index 93e01ad18..568ff4445 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/UserUpdatedEvent.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Events/UserUpdatedEvent.cs @@ -21,7 +21,9 @@ public enum UserUpdatedEventChanges TrnLookupStatus = 1 << 5, MobileNumber = 1 << 6, MiddleName = 1 << 7, - PreferredName = 1 << 8 + PreferredName = 1 << 8, + TrnVerificationLevel = 1 << 9, + NationalInsuranceNumber = 1 << 10, } public enum UserUpdatedEventSource diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/IdentityLinkGenerator.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/IdentityLinkGenerator.cs index 42e4d3438..16d825952 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/IdentityLinkGenerator.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/IdentityLinkGenerator.cs @@ -122,6 +122,10 @@ protected virtual string Page(string pageName, bool authenticationJourneyRequire public string RegisterNoAccount() => Page("/SignIn/Register/NoAccount"); + public string ElevateLanding() => Page("/SignIn/Elevate/Landing"); + + public string ElevateCheckAnswers() => Page("/SignIn/Elevate/CheckAnswers"); + public string Account(ClientRedirectInfo? clientRedirectInfo) => Page("/Account/Index", authenticationJourneyRequired: false) .SetQueryParam(ClientRedirectInfo.QueryParameterName, clientRedirectInfo); diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/CoreSignInJourneyWithTrnLookup.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/CoreSignInJourneyWithTrnLookup.cs index 9bcb6c39c..04aba7230 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/CoreSignInJourneyWithTrnLookup.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/CoreSignInJourneyWithTrnLookup.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using TeacherIdentity.AuthServer.Models; using TeacherIdentity.AuthServer.Oidc; using TeacherIdentity.AuthServer.Services.BackgroundJobs; using User = TeacherIdentity.AuthServer.Models.User; @@ -57,7 +58,7 @@ await _backgroundJobScheduler.Enqueue( AuthenticationState.IttProviderName, AuthenticationState.StatedTrn, client.DisplayName, - AuthenticationState.OAuthState.TrnRequirementType == Models.TrnRequirementType.Required)); + AuthenticationState.OAuthState.TrnRequirementType == TrnRequirementType.Required)); } } } @@ -98,6 +99,18 @@ protected override bool IsFinished() => AuthenticationState.TrnLookupStatus.HasValue && AuthenticationState.TrnLookup == AuthenticationState.TrnLookupState.Complete; + public override bool IsCompleted() + { + var finished = IsFinished(); + + if (finished && AuthenticationState.RequiresTrnVerificationLevelElevation == true) + { + return false; + } + + return finished; + } + public override bool CanAccessStep(string step) => step switch { CoreSignInJourney.Steps.CheckAnswers => (AreAllQuestionsAnswered() || FoundATrn) && AuthenticationState.ContactDetailsVerified, @@ -113,8 +126,22 @@ protected override bool IsFinished() => _ => base.CanAccessStep(step) }; + public override string GetNextStepUrl(string currentStep) => + currentStep switch + { + ElevateTrnVerificationLevelJourney.Steps.Landing => ElevateTrnVerificationLevelJourney.GetStartStepUrl(LinkGenerator), + _ => base.GetNextStepUrl(currentStep) + }; + protected override string? GetNextStep(string currentStep) { + // If we've signed a user in successfully and the TrnMatchPolicy is Strict + // but the user's TrnVerificationLevel is Low (or null) we need to switch to the 'elevate' journey + if (IsFinished() && AuthenticationState.RequiresTrnVerificationLevelElevation == true) + { + return ElevateTrnVerificationLevelJourney.GetStartStepUrl(LinkGenerator); + } + var shouldCheckAnswers = (AreAllQuestionsAnswered() || FoundATrn) && !AuthenticationState.ExistingAccountFound; return (currentStep, AuthenticationState) switch diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ElevateTrnVerificationLevelJourney.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ElevateTrnVerificationLevelJourney.cs new file mode 100644 index 000000000..30a0a6b30 --- /dev/null +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ElevateTrnVerificationLevelJourney.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; + +namespace TeacherIdentity.AuthServer.Journeys; + +public class ElevateTrnVerificationLevelJourney : SignInJourney +{ + private readonly TrnLookupHelper _trnLookupHelper; + + public ElevateTrnVerificationLevelJourney( + TrnLookupHelper trnLookupHelper, + HttpContext httpContext, + IdentityLinkGenerator linkGenerator, + UserHelper userHelper) : + base(httpContext, linkGenerator, userHelper) + { + _trnLookupHelper = trnLookupHelper; + } + + public static string GetStartStepUrl(IdentityLinkGenerator linkGenerator) => linkGenerator.ElevateLanding(); + + public async Task LookupTrn() + { + var trn = await _trnLookupHelper.LookupTrn(AuthenticationState); + Debug.Assert(AuthenticationState.TrnVerificationElevationSuccessful.HasValue); + + if (trn is not null) + { + Debug.Assert(AuthenticationState.TrnVerificationElevationSuccessful == true); + await UserHelper.ElevateTrnVerificationLevel(AuthenticationState.UserId!.Value, trn, AuthenticationState.NationalInsuranceNumber!); + } + else + { + Debug.Assert(AuthenticationState.TrnVerificationElevationSuccessful == false); + await UserHelper.SetNationalInsuranceNumber(AuthenticationState.UserId!.Value, AuthenticationState.NationalInsuranceNumber!); + } + } + + public override bool CanAccessStep(string step) => step switch + { + Steps.Landing => true, + CoreSignInJourneyWithTrnLookup.Steps.NiNumber => true, + CoreSignInJourneyWithTrnLookup.Steps.Trn => AuthenticationState.HasNationalInsuranceNumber == true, + Steps.CheckAnswers => AuthenticationState.HasNationalInsuranceNumber == true && AuthenticationState.StatedTrn is not null, + _ => false + }; + + protected override string? GetNextStep(string currentStep) => currentStep switch + { + Steps.Landing => CoreSignInJourneyWithTrnLookup.Steps.NiNumber, + CoreSignInJourneyWithTrnLookup.Steps.NiNumber => CoreSignInJourneyWithTrnLookup.Steps.Trn, + CoreSignInJourneyWithTrnLookup.Steps.Trn => Steps.CheckAnswers, + _ => null + }; + + protected override string? GetPreviousStep(string currentStep) => currentStep switch + { + CoreSignInJourneyWithTrnLookup.Steps.NiNumber => Steps.Landing, + CoreSignInJourneyWithTrnLookup.Steps.Trn => CoreSignInJourneyWithTrnLookup.Steps.NiNumber, + Steps.CheckAnswers => CoreSignInJourneyWithTrnLookup.Steps.Trn, + _ => null + }; + + protected override string GetStartStep() => Steps.Landing; + + protected override string GetStepUrl(string step) => step switch + { + Steps.Landing => LinkGenerator.ElevateLanding(), + CoreSignInJourneyWithTrnLookup.Steps.NiNumber => LinkGenerator.RegisterNiNumber(), + CoreSignInJourneyWithTrnLookup.Steps.Trn => LinkGenerator.RegisterTrn(), + Steps.CheckAnswers => LinkGenerator.ElevateCheckAnswers(), + _ => throw new ArgumentException($"Unknown step: '{step}'.") + }; + + // We're done when we've done a lookup, successful or not, using the Strict TrnMatchPolicy + protected override bool IsFinished() => AuthenticationState.TrnVerificationElevationSuccessful.HasValue; + + public new static class Steps + { + public const string Landing = $"{nameof(ElevateTrnVerificationLevelJourney)}.{nameof(Landing)}"; + public const string CheckAnswers = $"{nameof(ElevateTrnVerificationLevelJourney)}.{nameof(CheckAnswers)}"; + } +} diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ServiceCollectionExtensions.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ServiceCollectionExtensions.cs index fcceb528b..d64438a54 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ServiceCollectionExtensions.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/ServiceCollectionExtensions.cs @@ -17,6 +17,8 @@ public static IServiceCollection AddSignInJourneyStateProvider(this IServiceColl return provider.GetSignInJourney(authenticationState, httpContext); }); + services.AddTransient(sp => (ElevateTrnVerificationLevelJourney)sp.GetRequiredService()); + services .AddTransient() .AddTransient() diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourney.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourney.cs index 9b7f0434e..736a76c46 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourney.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourney.cs @@ -154,7 +154,7 @@ public virtual string GetNextStepUrl(string currentStep) if (!CanAccessStep(nextStep)) { - throw new InvalidOperationException($"Next step is not accessible (step: '{nextStep}', EmailAddressVerified: {AuthenticationState.EmailAddressVerified}, MobileNumberVerified: {AuthenticationState.MobileNumberVerified})."); + throw new InvalidOperationException($"Next step is not accessible (step: '{nextStep}')."); } return GetStepUrl(nextStep); diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourneyProvider.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourneyProvider.cs index 12a2ffd7d..595a5238b 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourneyProvider.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/SignInJourneyProvider.cs @@ -6,6 +6,8 @@ public class SignInJourneyProvider { public SignInJourney GetSignInJourney(AuthenticationState authenticationState, HttpContext httpContext) { + var signInJourneyType = typeof(CoreSignInJourney); + if (authenticationState.TryGetOAuthState(out var oAuthState) && authenticationState.UserRequirements.RequiresTrnLookup()) { #pragma warning disable CS0612 // Type or member is obsolete @@ -15,16 +17,16 @@ public SignInJourney GetSignInJourney(AuthenticationState authenticationState, H } #pragma warning restore CS0612 // Type or member is obsolete - return authenticationState.HasTrnToken ? - ActivatorUtilities.CreateInstance(httpContext.RequestServices, httpContext) : - ActivatorUtilities.CreateInstance(httpContext.RequestServices, httpContext); + signInJourneyType = authenticationState.HasTrnToken ? typeof(TrnTokenSignInJourney) : + authenticationState.RequiresTrnVerificationLevelElevation == true ? typeof(ElevateTrnVerificationLevelJourney) : + typeof(CoreSignInJourneyWithTrnLookup); } if (authenticationState.UserRequirements.HasFlag(UserRequirements.StaffUserType)) { - return ActivatorUtilities.CreateInstance(httpContext.RequestServices, httpContext); + signInJourneyType = typeof(StaffSignInJourney); } - return ActivatorUtilities.CreateInstance(httpContext.RequestServices, httpContext); + return (SignInJourney)ActivatorUtilities.CreateInstance(httpContext.RequestServices, signInJourneyType, httpContext); } } diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/TrnLookupHelper.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/TrnLookupHelper.cs index cb7d2b1e2..bb4f52962 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/TrnLookupHelper.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/TrnLookupHelper.cs @@ -1,6 +1,7 @@ using System.Text; using System.Text.Json; using Azure.Storage.Blobs; +using TeacherIdentity.AuthServer.Models; using TeacherIdentity.AuthServer.Services.DqtApi; namespace TeacherIdentity.AuthServer.Journeys; @@ -36,9 +37,10 @@ public TrnLookupHelper( FindTeachersResponseResult? findTeachersResult; FindTeachersResponseResult[] findTeachersResults = Array.Empty(); + var trnMatchPolicy = (authenticationState.TryGetOAuthState(out var oAuthState) ? oAuthState.TrnMatchPolicy : null) ?? TrnMatchPolicy.Default; + try { - authenticationState.TryGetOAuthState(out var oAuthState); var lookupResponse = await _dqtApiClient.FindTeachers( new FindTeachersRequest() @@ -48,9 +50,9 @@ public TrnLookupHelper( FirstName = authenticationState.FirstName, LastName = authenticationState.LastName, IttProviderName = authenticationState.IttProviderName, - NationalInsuranceNumber = NormalizeNino(authenticationState.NationalInsuranceNumber), + NationalInsuranceNumber = User.NormalizeNationalInsuranceNumber(authenticationState.NationalInsuranceNumber), Trn = NormalizeTrn(authenticationState.StatedTrn), - TrnMatchPolicy = oAuthState?.TrnMatchPolicy + TrnMatchPolicy = trnMatchPolicy }, cts.Token); @@ -69,7 +71,7 @@ public TrnLookupHelper( (findTeachersResult, trnLookupStatus) = ResolveTrn(findTeachersResults, authenticationState); if (findTeachersResult is not null) { - await CheckDqtTeacherNames(findTeachersResult); + await LogMissingNamesOnMatchedDqtRecord(findTeachersResult); } authenticationState.OnTrnLookupCompleted(findTeachersResult, trnLookupStatus); @@ -89,16 +91,6 @@ public TrnLookupHelper( _ => (null, TrnLookupStatus.None) }; - private static string? NormalizeNino(string? nino) - { - if (string.IsNullOrEmpty(nino)) - { - return null; - } - - return new string(nino.Where(char.IsAsciiLetterOrDigit).ToArray()).ToUpper(); - } - private static string? NormalizeTrn(string? trn) { if (string.IsNullOrEmpty(trn)) @@ -109,7 +101,7 @@ public TrnLookupHelper( return new string(trn.Where(char.IsAsciiDigit).ToArray()); } - private async Task CheckDqtTeacherNames(FindTeachersResponseResult teacher) + private async Task LogMissingNamesOnMatchedDqtRecord(FindTeachersResponseResult teacher) { if (string.IsNullOrEmpty(teacher.FirstName) || string.IsNullOrEmpty(teacher.LastName)) { diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/UserHelper.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/UserHelper.cs index 0724f1f08..f5a49b821 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/UserHelper.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Journeys/UserHelper.cs @@ -3,12 +3,14 @@ using Azure.Storage.Blobs; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using TeacherIdentity.AuthServer.Events; using TeacherIdentity.AuthServer.Helpers; using TeacherIdentity.AuthServer.Models; using TeacherIdentity.AuthServer.Services.DqtApi; using TeacherIdentity.AuthServer.Services.UserVerification; using TeacherIdentity.AuthServer.Services.Zendesk; using ZendeskApi.Client.Models; +using User = TeacherIdentity.AuthServer.Models.User; namespace TeacherIdentity.AuthServer.Journeys; @@ -66,7 +68,7 @@ public async Task CreateUser(AuthenticationState authenticationState) _dbContext.Users.Add(user); - _dbContext.AddEvent(new Events.UserRegisteredEvent() + _dbContext.AddEvent(new UserRegisteredEvent() { ClientId = authenticationState.OAuthState?.ClientId, CreatedUtc = _clock.UtcNow, @@ -115,7 +117,7 @@ public async Task CreateUserWithTrnLookup(AuthenticationState authenticati await _trnTokenHelper.InvalidateTrnToken(authenticationState.TrnToken!, user.UserId); } - _dbContext.AddEvent(new Events.UserRegisteredEvent() + _dbContext.AddEvent(new UserRegisteredEvent() { ClientId = authenticationState.OAuthState?.ClientId, CreatedUtc = _clock.UtcNow, @@ -157,7 +159,7 @@ public async Task CreateUserWithTrnToken(AuthenticationState authenticatio await _trnTokenHelper.InvalidateTrnToken(authenticationState.TrnToken!, user.UserId); - _dbContext.AddEvent(new Events.UserRegisteredEvent() + _dbContext.AddEvent(new UserRegisteredEvent() { ClientId = authenticationState.OAuthState?.ClientId, CreatedUtc = _clock.UtcNow, @@ -246,7 +248,7 @@ public async Task CreateTrnResolutionZendeskTicket( var user = await _dbContext.Users.Where(u => u.UserId == userId).SingleAsync(); user.TrnLookupSupportTicketCreated = true; - _dbContext.AddEvent(new Events.TrnLookupSupportTicketCreatedEvent() + _dbContext.AddEvent(new TrnLookupSupportTicketCreatedEvent() { TicketId = ticketResponse.Ticket.Id, TicketComment = ticketComment, @@ -270,21 +272,70 @@ public async Task EnsureDqtUserNameMatch(User user, AuthenticationState authenti } } + public async Task ElevateTrnVerificationLevel(Guid userId, string trn, string nationalInsuranceNumber) + { + var user = await _dbContext.Users.SingleAsync(u => u.UserId == userId); + user.Trn = trn; + user.TrnVerificationLevel = TrnVerificationLevel.Medium; + + var changes = UserUpdatedEventChanges.TrnVerificationLevel; + + if (nationalInsuranceNumber != user.NationalInsuranceNumber) + { + user.NationalInsuranceNumber = nationalInsuranceNumber; + changes |= UserUpdatedEventChanges.NationalInsuranceNumber; + } + + _dbContext.AddEvent(new UserUpdatedEvent() + { + Changes = changes, + CreatedUtc = _clock.UtcNow, + Source = UserUpdatedEventSource.ChangedByUser, + UpdatedByClientId = null, + UpdatedByUserId = null, + User = user + }); + + await _dbContext.SaveChangesAsync(); + } + + public async Task SetNationalInsuranceNumber(Guid userId, string? nationalInsuranceNumber) + { + var user = await _dbContext.Users.SingleAsync(u => u.UserId == userId); + + if (User.NormalizeNationalInsuranceNumber(nationalInsuranceNumber) != User.NormalizeNationalInsuranceNumber(user.NationalInsuranceNumber)) + { + user.NationalInsuranceNumber = nationalInsuranceNumber; + + _dbContext.AddEvent(new UserUpdatedEvent() + { + Changes = UserUpdatedEventChanges.NationalInsuranceNumber, + CreatedUtc = _clock.UtcNow, + Source = UserUpdatedEventSource.ChangedByUser, + UpdatedByClientId = null, + UpdatedByUserId = null, + User = user + }); + + await _dbContext.SaveChangesAsync(); + } + } + private async Task AssignDqtUserName(Guid userId, TeacherInfo dqtUser) { var existingUser = await _dbContext.Users.SingleAsync(u => u.UserId == userId); - var changes = (existingUser.FirstName != dqtUser.FirstName ? Events.UserUpdatedEventChanges.FirstName : Events.UserUpdatedEventChanges.None) | - ((existingUser.MiddleName ?? string.Empty) != dqtUser.MiddleName ? Events.UserUpdatedEventChanges.MiddleName : Events.UserUpdatedEventChanges.None) | - (existingUser.LastName != dqtUser.LastName ? Events.UserUpdatedEventChanges.LastName : Events.UserUpdatedEventChanges.None); + var changes = (existingUser.FirstName != dqtUser.FirstName ? UserUpdatedEventChanges.FirstName : UserUpdatedEventChanges.None) | + ((existingUser.MiddleName ?? string.Empty) != dqtUser.MiddleName ? UserUpdatedEventChanges.MiddleName : UserUpdatedEventChanges.None) | + (existingUser.LastName != dqtUser.LastName ? UserUpdatedEventChanges.LastName : UserUpdatedEventChanges.None); existingUser.FirstName = dqtUser.FirstName; existingUser.MiddleName = dqtUser.MiddleName; existingUser.LastName = dqtUser.LastName; - _dbContext.AddEvent(new Events.UserUpdatedEvent() + _dbContext.AddEvent(new UserUpdatedEvent() { - Source = Events.UserUpdatedEventSource.DqtSynchronization, + Source = UserUpdatedEventSource.DqtSynchronization, CreatedUtc = _clock.UtcNow, Changes = changes, User = Events.User.FromModel(existingUser), diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/Mappings/UserMapping.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/Mappings/UserMapping.cs index 80af0ac90..ac757829f 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/Mappings/UserMapping.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/Mappings/UserMapping.cs @@ -30,6 +30,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(u => u.MergedWithUserId); builder.Property(u => u.MobileNumber).HasMaxLength(100); builder.Property(u => u.NormalizedMobileNumber).HasMaxLength(15); + builder.Ignore(u => u.EffectiveVerificationLevel); builder.HasIndex(u => u.NormalizedMobileNumber).IsUnique().HasDatabaseName(User.MobileNumberUniqueIndexName).HasFilter("is_deleted = false and normalized_mobile_number is not null"); builder.HasOne(u => u.MergedWithUser).WithMany(u => u.MergedUsers).HasForeignKey(u => u.MergedWithUserId); builder.HasOne(u => u.RegisteredWithClient).WithMany().HasForeignKey(u => u.RegisteredWithClientId).HasPrincipalKey(a => a.ClientId); diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/User.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/User.cs index 6f6d076f2..3d15c0654 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/User.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Models/User.cs @@ -36,7 +36,33 @@ public class User public string? MobileNumber { get; set; } public MobileNumber? NormalizedMobileNumber { get; set; } public bool TrnLookupSupportTicketCreated { get; set; } - public TrnVerificationLevel? TrnVerificationLevel { get; set; } public string? NationalInsuranceNumber { get; set; } + + public TrnVerificationLevel? EffectiveVerificationLevel + { + get + { + if (Trn is null) + { + return null; + } + + if (TrnVerificationLevel == Models.TrnVerificationLevel.Medium) + { + return Models.TrnVerificationLevel.Medium; + } + + if (TrnAssociationSource == Models.TrnAssociationSource.TrnToken || + TrnAssociationSource == Models.TrnAssociationSource.SupportUi) + { + return Models.TrnVerificationLevel.Medium; + } + + return Models.TrnVerificationLevel.Low; + } + } + + public static string NormalizeNationalInsuranceNumber(string? nationalInsuranceNumber) => + new string((nationalInsuranceNumber ?? string.Empty).Where(char.IsAsciiLetterOrDigit).ToArray()).ToUpper(); } diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml index a5d6db89e..fa9ea7ad8 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml @@ -3,7 +3,9 @@ @inject IConfiguration Configuration @model TeacherIdentity.AuthServer.Pages.SignIn.CompleteModel @{ - ViewBag.Title = !Model.CanAccessService ? "You cannot access this service yet" : + ViewBag.Title = Model.TrnVerificationElevationSuccessful == true ? "The information you gave has been verified" : + Model.TrnVerificationElevationSuccessful == false ? "The information you gave could not be verified" : + !Model.CanAccessService ? "You cannot access this service yet" : Model.FirstTimeSignInForEmail ? "You’ve created a DfE Identity account" : "You’ve signed in to your DfE Identity account"; @@ -30,7 +32,25 @@ @ViewBag.Title - @if (Model.TrnRequirementType == TrnRequirementType.Required) + @if (Model.TrnVerificationElevationSuccessful == true) + { +

You can now @Model.ClientDisplayName using your DfE Identity account.

+ } + else if (Model.TrnVerificationElevationSuccessful == false && Model.TrnRequirementType == TrnRequirementType.Required) + { +

You’ve signed in to your DfE Identity account but some of the additional information you gave could not be verified.

+

Email @(Configuration["SupportEmail"]) for help.

+ } + else if (Model.TrnVerificationElevationSuccessful == false && Model.TrnRequirementType == TrnRequirementType.Optional) + { +

You can still @Model.ClientDisplayName.

+ } + else if (Model.TrnMatchPolicy == TrnMatchPolicy.Strict && Model.Trn is null && Model.TrnRequirementType == TrnRequirementType.Required) + { +

You’ve created a DfE Identity account but some of the information you gave could not be verified.

+

Email @(Configuration["SupportEmail"]) for help.

+ } + else if (Model.TrnRequirementType == TrnRequirementType.Required) { if (Model.TrnLookupStatus == TrnLookupStatus.Found) { @@ -61,7 +81,7 @@ else {

We need to find your details in our records so you can use this service.

-

To fix this problem, please email our support team @(Configuration["SupportEmail"])

+

To fix this problem, please email our support team @(Configuration["SupportEmail"]).

} } else @@ -106,7 +126,10 @@

Continue to @Model.ClientDisplayName

} - Continue + @if (Model.CanAccessService) + { + Continue + }
} diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml.cs index 071f2fa05..7b45ab2f1 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Complete.cshtml.cs @@ -32,6 +32,8 @@ public CompleteModel( public string? Trn { get; set; } + public bool? TrnVerificationElevationSuccessful { get; set; } + public string? RedirectUri { get; set; } public string? ResponseMode { get; set; } @@ -42,6 +44,8 @@ public CompleteModel( public TrnRequirementType? TrnRequirementType { get; set; } + public TrnMatchPolicy? TrnMatchPolicy { get; set; } + public string? ClientDisplayName { get; set; } public bool TrnLookupSupportTicketCreated { get; set; } @@ -60,8 +64,10 @@ public async Task OnGet() Email = authenticationState.EmailAddress; FirstTimeSignInForEmail = authenticationState.FirstTimeSignInForEmail!.Value; Trn = authenticationState.Trn; + TrnVerificationElevationSuccessful = authenticationState.TrnVerificationElevationSuccessful; TrnLookupStatus = authenticationState.TrnLookupStatus; TrnRequirementType = authenticationState.OAuthState?.TrnRequirementType; + TrnMatchPolicy = authenticationState.OAuthState?.TrnMatchPolicy; var user = await _dbContext.Users.SingleAsync(u => u.UserId == authenticationState.UserId); TrnLookupSupportTicketCreated = user?.TrnLookupSupportTicketCreated == true; diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml new file mode 100644 index 000000000..2f4bbdb10 --- /dev/null +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml @@ -0,0 +1,38 @@ +@page "/sign-in/elevate/check-answers" +@model TeacherIdentity.AuthServer.Pages.SignIn.Elevate.CheckAnswers +@{ + ViewBag.Title = "Check your answers"; +} + +@section BeforeContent +{ + +} + +
+
+
+

@ViewBag.Title

+ + + + National Insurance number + @Model.NationalInsuranceNumber + + Change + + + + Teacher reference number (TRN) + @Model.Trn + + Change + + + + + Continue +
+
+
+ diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml.cs new file mode 100644 index 000000000..660d7149d --- /dev/null +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/CheckAnswers.cshtml.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using TeacherIdentity.AuthServer.Journeys; + +namespace TeacherIdentity.AuthServer.Pages.SignIn.Elevate; + +[CheckJourneyType(typeof(ElevateTrnVerificationLevelJourney))] +[CheckCanAccessStep(CurrentStep)] +public class CheckAnswers : PageModel +{ + private const string CurrentStep = ElevateTrnVerificationLevelJourney.Steps.CheckAnswers; + + private readonly ElevateTrnVerificationLevelJourney _journey; + + public CheckAnswers(ElevateTrnVerificationLevelJourney journey) + { + _journey = journey; + } + + public string BackLink => _journey.GetPreviousStepUrl(CurrentStep); + + public string Trn => _journey.AuthenticationState.StatedTrn!; + public string NationalInsuranceNumber => _journey.AuthenticationState.NationalInsuranceNumber!; + + public void OnGet() + { + } + + public async Task OnPost() + { + await _journey.LookupTrn(); + return await _journey.Advance(CurrentStep); + } +} diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml new file mode 100644 index 000000000..d33e82b11 --- /dev/null +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml @@ -0,0 +1,34 @@ +@page "/sign-in/elevate/landing" +@inject IConfiguration Configuration +@model TeacherIdentity.AuthServer.Pages.SignIn.Elevate.Landing +@{ + ViewBag.Title = "You need to give more information"; +} + +
+
+
+
+

@ViewBag.Title

+ +

You’ve signed in to your DfE Identity account.

+ +

To @Model.ClientDisplayName using your account, you need to give your:

+
    +
  • National Insurance number
  • +
  • teacher reference number (TRN)
  • +
+ + @if (Model.TrnRequirementType == TrnRequirementType.Required) + { +

+ If you cannot give this information, you will not be able to access your teaching qualifications. + Email @(Configuration["SupportEmail"]) for help. +

+ } + + Continue +
+
+
+
diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml.cs new file mode 100644 index 000000000..151864cf6 --- /dev/null +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Elevate/Landing.cshtml.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages; +using TeacherIdentity.AuthServer.Journeys; +using TeacherIdentity.AuthServer.Models; +using TeacherIdentity.AuthServer.Oidc; + +namespace TeacherIdentity.AuthServer.Pages.SignIn.Elevate; + +[CheckCanAccessStep(CurrentStep)] +public class Landing : PageModel +{ + private const string CurrentStep = ElevateTrnVerificationLevelJourney.Steps.Landing; + + private readonly SignInJourney _journey; + private readonly ICurrentClientProvider _currentClientProvider; + + public Landing( + SignInJourney journey, + ICurrentClientProvider currentClientProvider) + { + _journey = journey; + _currentClientProvider = currentClientProvider; + } + + public string? ClientDisplayName { get; set; } + + public TrnRequirementType TrnRequirementType { get; set; } + + public void OnGet() + { + } + + public async Task OnPost() => await _journey.Advance(CurrentStep); + + public async override Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + ClientDisplayName = (await _currentClientProvider.GetCurrentClient())!.DisplayName; + TrnRequirementType = _journey.AuthenticationState.OAuthState!.TrnRequirementType!.Value; + + await base.OnPageHandlerExecutionAsync(context, next); + } +} diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/EmailExists.cshtml b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/EmailExists.cshtml index bd828fe50..269bc43bf 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/EmailExists.cshtml +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/EmailExists.cshtml @@ -13,7 +13,7 @@

@ViewBag.Title

-

We’ve found a DfE Identity account for email (@Model.Email)

+

We’ve found a DfE Identity account for email @Model.Email

Sign in
diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/NiNumberPage.cshtml.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/NiNumberPage.cshtml.cs index 1cd1398d3..dd3dbfc4a 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/NiNumberPage.cshtml.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/NiNumberPage.cshtml.cs @@ -5,7 +5,7 @@ namespace TeacherIdentity.AuthServer.Pages.SignIn.Register; -[CheckJourneyType(typeof(CoreSignInJourneyWithTrnLookup))] +[CheckJourneyType(typeof(CoreSignInJourneyWithTrnLookup), typeof(ElevateTrnVerificationLevelJourney))] [CheckCanAccessStep(CurrentStep)] public class NiNumberPage : PageModel { @@ -35,7 +35,7 @@ public async Task OnPost(string submit) { if (submit == "ni_number_not_known") { - HttpContext.GetAuthenticationState().OnHasNationalInsuranceNumberSet(false); + _journey.AuthenticationState.OnHasNationalInsuranceNumberSet(false); } else { @@ -44,7 +44,7 @@ public async Task OnPost(string submit) return this.PageWithErrors(); } - HttpContext.GetAuthenticationState().OnNationalInsuranceNumberSet(NiNumber!); + _journey.AuthenticationState.OnNationalInsuranceNumberSet(NiNumber!); } return await _journey.Advance(CurrentStep); diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml index 3ccae5237..4f1515cdd 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml @@ -25,15 +25,18 @@ - - I do not know my TRN - -

You can continue without it

- - Continue without TRN - -
-
+ @if (Model.ShowContinueWithoutTrnButton) + { + + I do not know my TRN + +

You can continue without it

+ + Continue without TRN + +
+
+ } Continue diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml.cs index 49041b296..e618567cc 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/Pages/SignIn/Register/TrnPage.cshtml.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.RazorPages; using TeacherIdentity.AuthServer.Journeys; namespace TeacherIdentity.AuthServer.Pages.SignIn.Register; -[CheckJourneyType(typeof(CoreSignInJourneyWithTrnLookup))] +[CheckJourneyType(typeof(CoreSignInJourneyWithTrnLookup), typeof(ElevateTrnVerificationLevelJourney))] [CheckCanAccessStep(CurrentStep)] public class TrnPage : PageModel { @@ -26,6 +27,8 @@ public TrnPage(SignInJourney journey) [RegularExpression(@"\A\D*(\d{1}\D*){7}\D*\Z", ErrorMessage = "Your TRN number should contain 7 digits")] public string? StatedTrn { get; set; } + public bool ShowContinueWithoutTrnButton { get; set; } + public void OnGet() { SetDefaultInputValues(); @@ -33,9 +36,9 @@ public void OnGet() public async Task OnPost(string submit) { - if (submit == "trn_not_known") + if (submit == "trn_not_known" && ShowContinueWithoutTrnButton) { - HttpContext.GetAuthenticationState().OnHasTrnSet(false); + _journey.AuthenticationState.OnHasTrnSet(false); } else { @@ -44,12 +47,17 @@ public async Task OnPost(string submit) return this.PageWithErrors(); } - HttpContext.GetAuthenticationState().OnTrnSet(StatedTrn); + _journey.AuthenticationState.OnTrnSet(StatedTrn); } return await _journey.Advance(CurrentStep); } + public override void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + ShowContinueWithoutTrnButton = _journey.GetType() != typeof(ElevateTrnVerificationLevelJourney); + } + private void SetDefaultInputValues() { StatedTrn ??= _journey.AuthenticationState.StatedTrn; diff --git a/dotnet-authserver/src/TeacherIdentity.AuthServer/UserClaimHelper.cs b/dotnet-authserver/src/TeacherIdentity.AuthServer/UserClaimHelper.cs index cdc3d1094..10ec1fe9d 100644 --- a/dotnet-authserver/src/TeacherIdentity.AuthServer/UserClaimHelper.cs +++ b/dotnet-authserver/src/TeacherIdentity.AuthServer/UserClaimHelper.cs @@ -90,10 +90,7 @@ public async Task> GetPublicClaims(Guid userId, TrnMa if (trnMatchPolicy is not null) { var haveSufficientTrnMatch = user.Trn is not null && - (trnMatchPolicy == TrnMatchPolicy.Default || - user.TrnVerificationLevel == TrnVerificationLevel.Medium || - user.TrnAssociationSource == TrnAssociationSource.TrnToken || - user.TrnAssociationSource == TrnAssociationSource.SupportUi); + (trnMatchPolicy == TrnMatchPolicy.Default || user.EffectiveVerificationLevel == TrnVerificationLevel.Medium); if (haveSufficientTrnMatch) { @@ -106,7 +103,7 @@ public async Task> GetPublicClaims(Guid userId, TrnMa { var dqtPerson = await _dqtApiClient.GetTeacherByTrn(user.Trn!) ?? throw new Exception($"Could not find teacher with TRN: '{user.Trn}'."); var dqtRecordHasNino = !string.IsNullOrEmpty(dqtPerson.NationalInsuranceNumber); - var niNumber = dqtRecordHasNino ? dqtPerson.NationalInsuranceNumber : user.NationalInsuranceNumber; + var niNumber = User.NormalizeNationalInsuranceNumber(dqtRecordHasNino ? dqtPerson.NationalInsuranceNumber : user.NationalInsuranceNumber); AddClaimIfHaveValue(claims, CustomClaims.NiNumber, niNumber); claims.Add(new Claim(CustomClaims.TrnMatchNiNumber, dqtRecordHasNino.ToString())); } diff --git a/dotnet-authserver/src/TeacherIdentity.TestClient/Views/Home/Index.cshtml b/dotnet-authserver/src/TeacherIdentity.TestClient/Views/Home/Index.cshtml index 6218a51ec..f72063c1e 100644 --- a/dotnet-authserver/src/TeacherIdentity.TestClient/Views/Home/Index.cshtml +++ b/dotnet-authserver/src/TeacherIdentity.TestClient/Views/Home/Index.cshtml @@ -7,7 +7,7 @@
  • - + Core + TRN lookup (Access your teaching qualifications)
  • @@ -16,6 +16,11 @@ Core + TRN lookup (NPQ) +
  • + + Core + TRN lookup (Claim) + +