using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using System; using System.Web; using SecureCore.Services; using SecureCore.Authentication; namespace SecureCore.Controllers { [Route("[controller]")] [ApiController] public class AuthController : Controller { //TODO: Login will only ever return messages like "Wrong username / password." whereas register can return messages like "User exists.", "Password to weak", or "Password in top 100 most used.". [HttpPost("login")] [AcceptVerbs("POST")] public IActionResult Login([FromBody] UserInformation.LoginData info) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); //TODO: see if the request has an active session key so we don't spam out new keys like mad try { if (HttpContext.Request.Cookies.ContainsKey("Session")) if (SessionManager.IsSessionTokenValid(HttpContext.Request.Cookies["Session"], connectionString)) return Ok("Logged in\n"); //Verify that the username provided is valid, i.e. no whitespace, special characters, etc. var result = UserDataService.IsUsernameValid(info.Username); //If the name isn't valid, return the error message to the user. if (!result.IsValid) return Unauthorized(result.Message); //Next try to get the user's login data, if the fuction returns empty strings, then the user isn't a registered name. var (password, salt) = PasswordManager.GetPasswordHashAndSalt(info.Username, connectionString); //If the user name isn't in the system, then simply return a generic error message about something not being right. if (password == string.Empty) return Unauthorized("User name or password is not correct."); result = PasswordManager.IsPasswordValid(info.Password); if (!result.IsValid) return Unauthorized(result.Message); if(!PasswordManager.IsPasswordAMatch(info.Password, salt, password)) return Unauthorized("User name or password is not correct."); var sessionToken = SessionManager.CreateSessionToken(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; var ip = PasswordManager.HashStringData(Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(), salt); var cookieOptions = GetCookieOptions(); SessionManager.Login(info.Username, sessionToken, cookieOptions.Expires.Value.UtcDateTime, agent, ip, connectionString); HttpContext.Response.Cookies.Append("Session", sessionToken, cookieOptions); return Ok($"Logged in success{Environment.NewLine}"); } catch(Exception ex) { //TODO: Log this event. return Unauthorized("An error has occured trying to process your request, please try again in a few minutes."); } } [HttpPost("Register")] [AcceptVerbs("POST")] public IActionResult Register([FromBody] UserInformation.RegistrationData info) { //Verify that the username provided is valid, i.e. no whitespace, special characters, etc. var result = UserDataService.IsUsernameValid(info.Username); //If the name isn't valid, return the error message to the user. if (!result.IsValid) return Unauthorized(result.Message); //Validate the password, make sure its not to long or short, etc. result = PasswordManager.IsPasswordValid(info.Password); if (!result.IsValid) return Unauthorized(result.Message); AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); var (hash, salt) = PasswordManager.HashPassword(info.Password); var sessionToken = SessionManager.CreateSessionToken(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; var ip = PasswordManager.HashStringData(Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(), salt); try { var cookieOptions = GetCookieOptions(); var i = UserDataService.RegisterNewUser(info.Username, info.Email, hash, salt, sessionToken, cookieOptions.Expires.Value.UtcDateTime, agent, ip, connectionString); HttpContext.Response.Cookies.Append("Session", sessionToken, cookieOptions); return Ok($"New User ID: {i}"); } catch (Exception e) { return Unauthorized(e.Message); } } [HttpPost("Logout")] [AcceptVerbs("POST")] public IActionResult Logout() { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); SessionManager.Logout(HttpContext.Request.Cookies["Session"], connectionString); HttpContext.Response.Cookies.Delete("Session"); return Ok(); } [HttpPost("ResetPassword")] [AcceptVerbs("POST")] public IActionResult ResetPassword([FromQuery] string token, [FromBody] string password) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); try { if (!SessionManager.IsSessionTokenValid(token, connectionString, true)) return Unauthorized("Token invalid"); var (Hash, Salt) = PasswordManager.HashPassword(password); PasswordManager.ResetPassword(Hash, Salt, token, connectionString); return Ok("Reset successful\n"); } catch (Exception e) { return Unauthorized(e.Message); } } [HttpPost("CreatePasswordResetLink")] [AcceptVerbs("POST")] public IActionResult CreatePasswordResetLink([FromBody] string email) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); var token = SessionManager.CreateSessionToken(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; var ip = PasswordManager.HashStringData(Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString()); try { PasswordManager.InsertPasswordResetRequest(email, token, DateTime.Now.AddHours(1), agent, ip, connectionString); token = HttpUtility.UrlEncode(token); return Ok($"192.168.255.200:5000/auth/ResetPassword?token={token}{Environment.NewLine}"); } catch(Exception e) { return Unauthorized($"{e.Message}{Environment.NewLine}"); } } private CookieOptions GetCookieOptions() { return new CookieOptions { //Domain = "copyrightcrusader.org", Expires = DateTime.Now.AddDays(7), //HttpOnly = true, //Secure = true, Path = "/", SameSite = SameSiteMode.Strict }; } } //TODO: Read more https://www.valentinog.com/blog/cookies/ // And this https://blog.webf.zone/ultimate-guide-to-http-cookies-2aa3e083dbae }