public async Task <IActionResult> Send(TotpRequest request) { var userId = User.FindSubjectId(); if (string.IsNullOrEmpty(userId)) { return(Forbid()); } switch (request.Channel) { case TotpDeliveryChannel.Sms: var result = await TotpService.Send(options => options.UsePrincipal(User).WithMessage(request.Message).UsingSms().WithPurpose(request.Purpose)); if (!result.Success) { ModelState.AddModelError(nameof(request.Channel), result.Errors.FirstOrDefault() ?? "An error occured."); return(BadRequest(new ValidationProblemDetails(ModelState))); } break; case TotpDeliveryChannel.Email: case TotpDeliveryChannel.Telephone: case TotpDeliveryChannel.Viber: default: return(StatusCode(405)); } return(NoContent()); }
public async Task <IEndpointResult> ProcessAsync(HttpContext httpContext) { Logger.LogInformation($"[{nameof(InitRegistrationEndpoint)}] Started processing trusted device registration initiation endpoint."); var isPostRequest = HttpMethods.IsPost(httpContext.Request.Method); var isApplicationFormContentType = httpContext.Request.HasApplicationFormContentType(); // Validate HTTP request type and method. if (!isPostRequest || !isApplicationFormContentType) { return(Error(OidcConstants.TokenErrors.InvalidRequest, "Request must be of type 'POST' and have a Content-Type equal to 'application/x-www-form-urlencoded'.")); } // Ensure that a valid 'Authorization' header exists. var tokenUsageResult = await Token.Validate(httpContext); if (!tokenUsageResult.TokenFound) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "No access token is present in the request.")); } // Validate request data and access token. var parameters = (await httpContext.Request.ReadFormAsync()).AsNameValueCollection(); var requestValidationResult = await Request.Validate(parameters, tokenUsageResult.Token); if (requestValidationResult.IsError) { return(Error(requestValidationResult.Error, requestValidationResult.ErrorDescription)); } // Ensure device is not already registered or belongs to any other user. var existingDevice = await UserDeviceStore.GetByDeviceId(requestValidationResult.DeviceId); var isNewDeviceOrOwnedByUser = existingDevice == null || existingDevice.UserId.Equals(requestValidationResult.UserId, StringComparison.OrdinalIgnoreCase); if (!isNewDeviceOrOwnedByUser) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "Device does not belong to the this user.")); } // Ensure that the principal has declared a phone number which is also confirmed. // We will get these 2 claims by retrieving the identity resources from the store (using the requested scopes existing in the access token) and then calling the profile service. // This will help us make sure that the 'phone' scope was requested and finally allowed in the token endpoint. var identityResources = await ResourceStore.FindEnabledIdentityResourcesByScopeAsync(requestValidationResult.RequestedScopes); var resources = new Resources(identityResources, Enumerable.Empty <ApiResource>(), Enumerable.Empty <ApiScope>()); var validatedResources = new ResourceValidationResult(resources); if (!validatedResources.Succeeded) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "Identity resources could be validated.")); } var requestedClaimTypes = resources.IdentityResources.SelectMany(x => x.UserClaims).Distinct(); var profileDataRequestContext = new ProfileDataRequestContext(requestValidationResult.Principal, requestValidationResult.Client, IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint, requestedClaimTypes) { RequestedResources = validatedResources }; await ProfileService.GetProfileDataAsync(profileDataRequestContext); var profileClaims = profileDataRequestContext.IssuedClaims; var phoneNumberClaim = profileClaims.FirstOrDefault(x => x.Type == JwtClaimTypes.PhoneNumber); var phoneNumberVerifiedClaim = profileClaims.FirstOrDefault(x => x.Type == JwtClaimTypes.PhoneNumberVerified); if (string.IsNullOrWhiteSpace(phoneNumberClaim?.Value) || phoneNumberVerifiedClaim == null || (bool.TryParse(phoneNumberVerifiedClaim.Value, out var phoneNumberVerified) && !phoneNumberVerified)) { return(Error(OidcConstants.ProtectedResourceErrors.InvalidToken, "User does not have a phone number or the phone number is not verified.")); } var otpAuthenticatedValue = profileClaims.FirstOrDefault(x => x.Type == BasicClaimTypes.OtpAuthenticated)?.Value; var otpAuthenticated = !string.IsNullOrWhiteSpace(otpAuthenticatedValue) && bool.Parse(otpAuthenticatedValue); if (!otpAuthenticated) { // Send OTP code. void messageBuilder(TotpMessageBuilder message) { var builder = message.UsePrincipal(requestValidationResult.Principal).WithMessage(IdentityMessageDescriber.DeviceRegistrationCodeMessage(existingDevice?.Name, requestValidationResult.InteractionMode)); if (requestValidationResult.DeliveryChannel == TotpDeliveryChannel.Sms) { builder.UsingSms(); } else { builder.UsingViber(); } builder.WithPurpose(Constants.TrustedDeviceOtpPurpose(requestValidationResult.UserId, requestValidationResult.DeviceId)); } var totpResult = await TotpService.Send(messageBuilder); if (!totpResult.Success) { return(Error(totpResult.Error)); } } // Create endpoint response. var response = await Response.Generate(requestValidationResult); Logger.LogInformation($"[{nameof(InitRegistrationEndpoint)}] Trusted device registration initiation endpoint success."); return(new InitRegistrationResult(response)); }