Created
March 27, 2022 18:24
-
-
Save Abubakr0904/fdf6215fda6a798b5a1f8280fb7ee2f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Ilmhub.Auth.Controllers | |
| { | |
| [Authorize] | |
| [SecurityHeaders( | |
| FontSources = SecurityHeadersFontSources.FONTAWESOME + SecurityHeadersFontSources.GLUWASTATIC, | |
| ScriptSources = SecurityHeadersScriptSources.JQUERY + SecurityHeadersScriptSources.BOOTSTRAP + SecurityHeadersScriptSources.INTERNAL, | |
| StyleSources = SecurityHeadersStyleSources.BOOTSTRAP + SecurityHeadersStyleSources.FONTAWESOME + SecurityHeadersStyleSources.GOOGLEFONTS)] | |
| public class AccountController : Controller | |
| { | |
| private readonly MessagingClient mMessagingClient; | |
| private readonly UserManager mUserManager; | |
| private readonly SignInManager mSignInManager; | |
| private readonly MessageStringProvider mMessageStringProvider; | |
| private readonly ILogger mLogger; | |
| private readonly IIdentityServerInteractionService mInteractionService; | |
| private readonly IStringLocalizer<AccountController> mLocalizer; | |
| private readonly ClientStore mClientStore; | |
| private readonly Options mOptions; | |
| public AccountController( | |
| MessagingClient messagingClient, | |
| UserManager userManager, | |
| SignInManager signInManager, | |
| MessageStringProvider messageStringProvider, | |
| ILogger<AccountController> logger, | |
| IIdentityServerInteractionService interactionService, | |
| IStringLocalizer<AccountController> localizer, | |
| ClientStore clientStore, | |
| IOptionsSnapshot<Options> optionsSnapshot) | |
| { | |
| mMessagingClient = messagingClient; | |
| mUserManager = userManager; | |
| mSignInManager = signInManager; | |
| mMessageStringProvider = messageStringProvider; | |
| mLogger = logger; | |
| mInteractionService = interactionService; | |
| mLocalizer = localizer; | |
| mClientStore = clientStore; | |
| mOptions = optionsSnapshot.Value; | |
| } | |
| // | |
| // GET: /Account/Signup | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public async Task<IActionResult> Signup() | |
| { | |
| if (mSignInManager.IsSignedIn(User)) | |
| { | |
| await mSignInManager.SignOutAsync(); | |
| } | |
| return View(); | |
| } | |
| // | |
| // POST: /Account/Signup | |
| [HttpPost] | |
| [AllowAnonymous] | |
| [ValidateAntiForgeryToken] | |
| public async Task<IActionResult> Signup(RegisterViewModel model) | |
| { | |
| if (!ModelState.IsValid) | |
| { | |
| return View(model); | |
| } | |
| var user = new User | |
| { | |
| UserName = model.Username, | |
| Email = model.Email | |
| }; | |
| var createUserResult = await mUserManager.CreateAsync(user, model.Password); | |
| if (!createUserResult.Succeeded) | |
| { | |
| foreach (var createUserError in createUserResult.Errors) | |
| { | |
| switch (createUserError.Code) | |
| { | |
| case "DuplicateUserName": | |
| ModelState.AddModelError(nameof(model.Username), mLocalizer["Duplicate Username"]); | |
| break; | |
| case "InvalidUserName": | |
| ModelState.AddModelError(nameof(model.Username), mLocalizer["Invalid Username"]); | |
| break; | |
| case "RestrictedUsername": | |
| ModelState.AddModelError(nameof(model.Username), mLocalizer["Restricted Username"]); | |
| break; | |
| case "DuplicateEmail": | |
| ModelState.AddModelError(nameof(model.Email), mLocalizer["This email is already taken"]); | |
| break; | |
| case "InvalidEmail": | |
| ModelState.AddModelError(nameof(model.Email), mLocalizer["Invalid Email"]); | |
| break; | |
| case "PasswordTooShort": | |
| ModelState.AddModelError(nameof(model.Password), mLocalizer["Password too short"]); | |
| break; | |
| case "PasswordRequiresNonAlphanumeric": | |
| ModelState.AddModelError(nameof(model.Password), mLocalizer["Password requires non-alphanumeric characters"]); | |
| break; | |
| case "PasswordRequiresDigit": | |
| ModelState.AddModelError(nameof(model.Password), mLocalizer["Password requires digit"]); | |
| break; | |
| case "PasswordRequiresLower": | |
| ModelState.AddModelError(nameof(model.Password), mLocalizer["Password requires lower case characters"]); | |
| break; | |
| case "PasswordRequiresUpper": | |
| ModelState.AddModelError(nameof(model.Password), mLocalizer["Password requires upper case characters"]); | |
| break; | |
| default: | |
| ModelState.AddModelError(string.Empty, mLocalizer["Something went wrong"]); | |
| break; | |
| } | |
| } | |
| return View(model); | |
| } | |
| string code = await mUserManager.GenerateEmailConfirmationTokenAsync(user); | |
| string link = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code }, protocol: "https"); | |
| string emailSubject = mMessageStringProvider.GetSignupConfirmEmailSubject(); | |
| string emailBody = mMessageStringProvider.GetEmailVerificationMessage(user.UserName, link); | |
| var emailRequest = new SendEmailRequest( | |
| userAccountID: Guid.Parse(user.Id), | |
| messageType: EMessageType.AccountVerificationRequest, | |
| messageSender: EMessageSender.Auth, | |
| email: model.Email, | |
| subject: emailSubject, | |
| message: emailBody); | |
| #pragma warning disable 4014 | |
| mMessagingClient.SendEmailAsync(emailRequest); | |
| #pragma warning restore 4014 | |
| return RedirectToAction("ConfirmEmailInstruction"); | |
| } | |
| // | |
| // GET: /Account/ConfirmEmailInstrucion | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult ConfirmEmailInstruction() | |
| { | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View(); | |
| } | |
| // | |
| // GET: /Account/Login | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public async Task<IActionResult> Login(string returnUrl = null) | |
| { | |
| if (mSignInManager.IsSignedIn(User)) | |
| { | |
| await mSignInManager.SignOutAsync(); | |
| var logoutID = await mInteractionService.CreateLogoutContextAsync(); | |
| var signoutContext = await mInteractionService.GetLogoutContextAsync(logoutID); | |
| var signinContext = await mInteractionService.GetAuthorizationContextAsync(returnUrl); | |
| var client = await mClientStore.FindClientByIdAsync(signinContext?.ClientId); | |
| LoggedoutViewModel loggedOutViewModel = new LoggedoutViewModel( | |
| automaticRedirectAfterSignOut: true, | |
| clientName: client?.ClientName, | |
| signOutIframeUrl: signoutContext?.SignOutIFrameUrl, | |
| postLogoutRedirectUri: signinContext?.RedirectUri); | |
| return View("LoggedOut", loggedOutViewModel); | |
| } | |
| ViewData["ReturnUrl"] = returnUrl; | |
| return View(); | |
| } | |
| // | |
| // POST: /Account/Login | |
| [HttpPost] | |
| [AllowAnonymous] | |
| [ValidateAntiForgeryToken] | |
| public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) | |
| { | |
| ViewData["ReturnUrl"] = returnUrl; | |
| if (!ModelState.IsValid) | |
| { | |
| return View(model); | |
| } | |
| var emailValidator = new EmailAddressAttribute(); | |
| var isValidEmail = emailValidator.IsValid(model.Email); | |
| User user; | |
| if (isValidEmail) | |
| { | |
| user = await mUserManager.FindByEmailAsync(model.Email); | |
| } | |
| else | |
| { | |
| user = await mUserManager.FindByNameAsync(model.Email); | |
| } | |
| if (user != null) | |
| { | |
| // This doesn't count login failures towards account lockout | |
| // To enable password failures to trigger account lockout, set lockoutOnFailure: true | |
| var result = await mSignInManager.PasswordSignInAsync(user, model.Password, isPersistent: false, lockoutOnFailure: true); | |
| if (result.Succeeded) | |
| { | |
| mLogger.LogInformation(1, "User logged in."); | |
| string redirectUrl = string.IsNullOrWhiteSpace(returnUrl) ? mOptions.GluwaProDashboardBaseUrl : returnUrl; | |
| return Redirect(redirectUrl); | |
| } | |
| if (result.IsLockedOut) | |
| { | |
| mLogger.LogWarning(2, "User account locked out."); | |
| ModelState.AddModelError(string.Empty, mLocalizer["You have tried too many times. Try again after {0} minutes.", 30]); | |
| return View(model); | |
| } | |
| if (user.EmailConfirmed) | |
| { | |
| ModelState.AddModelError(string.Empty, mLocalizer["Credentials you entered don't match our records."]); | |
| return View(model); | |
| } | |
| } | |
| ModelState.AddModelError(string.Empty, mLocalizer["Credentials you entered don't match our records or your email requires confirmation."]); | |
| return View(model); | |
| } | |
| [HttpGet] | |
| public async Task<IActionResult> Logout(string logoutID) | |
| { | |
| var context = await mInteractionService.GetLogoutContextAsync(logoutID); | |
| var model = new LogoutViewModel | |
| { | |
| LogoutID = logoutID | |
| }; | |
| if (!context.ShowSignoutPrompt) | |
| { | |
| return await Logout(model); | |
| } | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View(model); | |
| } | |
| // | |
| // POST: /Account/Logout | |
| [HttpPost] | |
| [ValidateAntiForgeryToken] | |
| public async Task<IActionResult> Logout(LogoutViewModel model) | |
| { | |
| var logout = await mInteractionService.GetLogoutContextAsync(model.LogoutID); | |
| var loggedOutModel = new LoggedoutViewModel | |
| ( | |
| automaticRedirectAfterSignOut: !string.IsNullOrWhiteSpace(logout?.PostLogoutRedirectUri), | |
| clientName: logout?.ClientName, | |
| signOutIframeUrl: logout?.SignOutIFrameUrl, | |
| postLogoutRedirectUri: logout?.PostLogoutRedirectUri | |
| ); | |
| await mSignInManager.SignOutAsync(); | |
| mLogger.LogInformation(4, "User logged out."); | |
| return View("LoggedOut", loggedOutModel); | |
| } | |
| // | |
| // GET: /Account/ConfirmEmail | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public async Task<IActionResult> ConfirmEmail(string userId, string code, int? rp) | |
| { | |
| if (userId == null || code == null) | |
| { | |
| return View("Error"); | |
| } | |
| var user = await mUserManager.FindByIdIncludeAllStatusAsync(new Guid(userId)); | |
| if (user == null) | |
| { | |
| mLogger.LogWarning($"User with {userId} not found."); | |
| return View("Error"); | |
| } | |
| if (user.EmailConfirmed) | |
| { | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View("EmailAlreadyConfirmed"); | |
| } | |
| var result = await mUserManager.ConfirmEmailAsync(user, code); | |
| if (!result.Succeeded) | |
| { | |
| mLogger.LogWarning($"Error while confirming email {user.Email}: {result.Errors.ToJson()}"); | |
| return View("Error"); | |
| } | |
| // Send reset password email if this email confirmation has been prompted by a password reset request | |
| var bDisplayResetPasswordMessage = false; | |
| if (rp.HasValue && rp == 1) | |
| { | |
| var resetPasswordCode = await mUserManager.GeneratePasswordResetTokenAsync(user); | |
| var resetPasswordCallbackUrl = Url.Action(nameof(ResetPassword), "Account", new { code = resetPasswordCode }, protocol: "https"); | |
| var emailRequest = new SendEmailRequest( | |
| userAccountID: Guid.Parse(user.Id), | |
| messageType: EMessageType.ResetPasswordRequest, | |
| messageSender: EMessageSender.Auth, | |
| email: user.Email, | |
| subject: mMessageStringProvider.GetResetPasswordEmailSubject(), | |
| message: mMessageStringProvider.GetResetPasswordEmailMessage(user.UserName, resetPasswordCallbackUrl)); | |
| var sendEmailResult = await mMessagingClient.SendEmailAsync(emailRequest); | |
| if (sendEmailResult.IsSuccess) | |
| { | |
| bDisplayResetPasswordMessage = true; | |
| } | |
| } | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View("ConfirmEmail", bDisplayResetPasswordMessage); | |
| } | |
| // | |
| // GET: /Account/ForgotPassword | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult ForgotPassword() | |
| { | |
| ViewData["Title"] = mLocalizer["Forgot your password?"]; | |
| ViewData["GluwaProDashboardBaseUrl"] = $"{mOptions.GluwaProDashboardBaseUrl}/ApiKey"; | |
| return View("ForgotPassword"); | |
| } | |
| // | |
| // POST: /Account/ForgotPassword | |
| [HttpPost] | |
| [AllowAnonymous] | |
| [ValidateAntiForgeryToken] | |
| public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model) | |
| { | |
| ViewData["GluwaProDashboardBaseUrl"] = $"{mOptions.GluwaProDashboardBaseUrl}/ApiKey"; | |
| if (!ModelState.IsValid) | |
| { | |
| return View(model); | |
| } | |
| var user = await mUserManager.FindByEmailAsync(model.Email); | |
| if (user == null || !(await mUserManager.IsEmailConfirmedAsync(user))) | |
| { | |
| ModelState.AddModelError(nameof(model.Email), mLocalizer["This email does not exist in our system."]); | |
| return View(model); | |
| } | |
| var code = await mUserManager.GeneratePasswordResetTokenAsync(user); | |
| var callbackUrl = Url.Action(nameof(ResetPassword), "Account", new { code }, protocol: HttpContext.Request.Scheme); | |
| var emailRequest = new SendEmailRequest( | |
| userAccountID: Guid.Parse(user.Id), | |
| messageType: EMessageType.ResetPasswordRequest, | |
| messageSender: EMessageSender.Auth, | |
| email: model.Email, | |
| subject: mMessageStringProvider.GetResetPasswordEmailSubject(), | |
| message: mMessageStringProvider.GetResetPasswordEmailMessage(user.UserName, callbackUrl)); | |
| var result = await mMessagingClient.SendEmailAsync(emailRequest); | |
| if (!result.IsSuccess) | |
| { | |
| switch (result.Error.Code) | |
| { | |
| case EEmailErrorCode.TooManyRequests: | |
| ModelState.AddModelError(string.Empty, mLocalizer["You have tried too many times. Try again after {0} minutes.", 60]); | |
| return View(model); | |
| default: | |
| ModelState.AddModelError(string.Empty, mLocalizer["Something went wrong"]); | |
| return View(model); | |
| } | |
| } | |
| return View("ForgotPasswordConfirmation", model); | |
| } | |
| // | |
| // GET: /Account/ResetPassword | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult ResetPassword(string code = null) | |
| { | |
| return code == null ? View("Error") : View(); | |
| } | |
| // | |
| // POST: /Account/ResetPassword | |
| [HttpPost] | |
| [AllowAnonymous] | |
| [ValidateAntiForgeryToken] | |
| public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model, [FromServices] PersistedGrantDbContext dbContext) | |
| { | |
| if (!ModelState.IsValid) | |
| { | |
| return View(model); | |
| } | |
| User user; | |
| var emailValidator = new EmailAddressAttribute(); | |
| if (emailValidator.IsValid(model.UsernameOrEmail)) | |
| { | |
| user = await mUserManager.FindByEmailAsync(model.UsernameOrEmail); | |
| } | |
| else | |
| { | |
| user = await mUserManager.FindByNameAsync(model.UsernameOrEmail); | |
| } | |
| if (user == null) | |
| { | |
| // Don't reveal that the user does not exist | |
| return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); | |
| } | |
| var result = await mUserManager.ResetPasswordAsync(user, model.Code, model.Password); | |
| if (result.Succeeded) | |
| { | |
| await removePersistantGrantsInternalAsync(dbContext, user.Id.ToLowerInvariant()); | |
| if (string.IsNullOrWhiteSpace(user.Email)) | |
| { | |
| var smsRequest = new SendSmsRequest( | |
| userAccountID: Guid.Parse(user.Id), | |
| messageType: EMessageType.ResetPasswordNotification, | |
| messageSender: EMessageSender.Auth, | |
| phoneNumber: user.PhoneNumber, | |
| message: mMessageStringProvider.GetPasswordChangedNotificationSms()); | |
| await mMessagingClient.SendSmsAsync(smsRequest); | |
| } | |
| else | |
| { | |
| var emailRequest = new SendEmailRequest( | |
| userAccountID: Guid.Parse(user.Id), | |
| messageType: EMessageType.ResetPasswordNotification, | |
| messageSender: EMessageSender.Auth, | |
| email: user.Email, | |
| subject: mMessageStringProvider.GetPasswordChangedEmailSubject(), | |
| message: mMessageStringProvider.GetPasswordChangedNotificationEmailMessage(user.UserName)); | |
| await mMessagingClient.SendEmailAsync(emailRequest); | |
| } | |
| return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); | |
| } | |
| addErrors(result); | |
| return View(); | |
| } | |
| // | |
| // GET: /Account/ResetPasswordConfirmation | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult ResetPasswordConfirmation() | |
| { | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View(); | |
| } | |
| // | |
| // GET: /Account/ChangeEmailConfirmation | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult ChangeEmailConfirmation() | |
| { | |
| ViewData["GluwaProDashboardBaseUrl"] = mOptions.GluwaProDashboardBaseUrl; | |
| return View(); | |
| } | |
| // | |
| // GET /Account/AccessDenied | |
| [HttpGet] | |
| [AllowAnonymous] | |
| public IActionResult AccessDenied() | |
| { | |
| return View(); | |
| } | |
| #region Helpers | |
| private async Task removePersistantGrantsInternalAsync(PersistedGrantDbContext dbContext, string normalizedUserID) | |
| { | |
| var grants = dbContext.PersistedGrants.Where(g => g.SubjectId == normalizedUserID && g.Type == GrantTypes.RefreshToken); | |
| dbContext.PersistedGrants.RemoveRange(grants); | |
| await dbContext.SaveChangesAsync(); | |
| } | |
| private void addErrors(IdentityResult result) | |
| { | |
| foreach (var error in result.Errors) | |
| { | |
| ModelState.AddModelError(string.Empty, error.Description); | |
| } | |
| } | |
| #endregion | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment