209 lines
9.2 KiB
C#
209 lines
9.2 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Http;
|
|
using System;
|
|
using System.Web;
|
|
using SecureCore.Services;
|
|
using SecureCore.Authentication;
|
|
using SecureCore.Models;
|
|
|
|
namespace SecureCore.Controllers
|
|
{
|
|
[Route("[controller]")]
|
|
[ApiController]
|
|
public class AuthController : Controller
|
|
{
|
|
[HttpPost("IsLoggedIn")]
|
|
[AcceptVerbs("POST")]
|
|
public IActionResult IsLoggedIn()
|
|
{
|
|
return Ok();
|
|
}
|
|
//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] LoginInfo info)
|
|
{
|
|
AppSettingsManager.TryGetConnectionString("MainDataConnectionString", out string connectionString);
|
|
|
|
try
|
|
{
|
|
if (SessionManager.IsSessionTokenValid(HttpContext, 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);
|
|
//Now make sure the password is valid.
|
|
result = PasswordManager.IsPasswordValid(info.Password);
|
|
|
|
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.");
|
|
|
|
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(SessionManager.SessionCookieName, 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(RegistrationInfo info)
|
|
{
|
|
AppSettingsManager.TryGetConnectionString("MainDataConnectionString", out string connectionString);
|
|
//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);
|
|
|
|
result = UserDataService.IsEmailValid(info.Email);
|
|
|
|
if (!result.IsValid) return Unauthorized(result.Message);
|
|
|
|
try
|
|
{
|
|
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);
|
|
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(SessionManager.SessionCookieName, sessionToken, cookieOptions);
|
|
|
|
return Ok($"New User ID: {i}");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
//TODO: redo the message that sent back to the client. There could be more than just a SQL Server error here.
|
|
return Unauthorized(e.Message);
|
|
}
|
|
}
|
|
|
|
[HttpPost("Logout")]
|
|
[AcceptVerbs("POST")]
|
|
public IActionResult Logout()
|
|
{
|
|
if (!HttpContext.Request.Cookies.ContainsKey("Session")) return Ok();
|
|
|
|
AppSettingsManager.TryGetConnectionString("MainDataConnectionString", out string connectionString);
|
|
|
|
try
|
|
{
|
|
SessionManager.Logout(HttpContext.Request.Cookies[SessionManager.SessionCookieName], connectionString);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
//TODO: decide what to do here. If the SQL fails to clear the token from the database, do we want to clear the token cookie?
|
|
}
|
|
|
|
HttpContext.Response.Cookies.Delete(SessionManager.SessionCookieName);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
[HttpPost("ResetPassword")]
|
|
[AcceptVerbs("POST")]
|
|
public IActionResult ResetPassword([FromQuery] string token, [FromBody] string password)
|
|
{
|
|
AppSettingsManager.TryGetConnectionString("MainDataConnectionString", out string connectionString);
|
|
|
|
try
|
|
{
|
|
if (!SessionManager.IsSessionTokenValid(token, connectionString, true)) return Unauthorized("This link has expired, please request a new email reset link.");
|
|
|
|
var (IsValid, Message) = PasswordManager.IsPasswordValid(password);
|
|
|
|
if (!IsValid) return Unauthorized(Message);
|
|
|
|
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.TryGetConnectionString("MainDataConnectionString", out string connectionString);
|
|
//Validate the email input.
|
|
var (IsValid, Message) = UserDataService.IsEmailValid(email);
|
|
|
|
if (!IsValid) return Unauthorized(Message);
|
|
|
|
//If its a properly formatted email, then check to see if its in use by anyone.
|
|
if (!UserDataService.IsEmailInUse(email, connectionString)) return Unauthorized("Email is not valid.");
|
|
|
|
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);
|
|
|
|
AppSettingsManager.TryGetSettingString("PasswordSettings.BaseURL", out string baseAddress);
|
|
|
|
token = HttpUtility.UrlEncode(token);
|
|
//TODO: Email the link to the supplied email.
|
|
return Ok($"{baseAddress}/ResetPassword?token={token}{Environment.NewLine}");
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
return Unauthorized($"{e.Message}{Environment.NewLine}");
|
|
}
|
|
}
|
|
|
|
//TODO: Review https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-5.0 and https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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
|
|
}
|