diff --git a/src/Server/Controllers/AuthenticationController.cs b/src/Server/Controllers/AuthenticationController.cs index 4345e4c..2598815 100755 --- a/src/Server/Controllers/AuthenticationController.cs +++ b/src/Server/Controllers/AuthenticationController.cs @@ -2,11 +2,12 @@ using Auth.Services; using Auth.Services.DatabaseService; using Auth.Entities; +using Auth.DTO; using System.Web.Http; namespace Auth.Controllers { [ApiController] - [Route("api/account/")] + [Route("api/")] public class AuthenticationController : MistoxControllerBase { EmailService _emailContext; @@ -15,113 +16,6 @@ namespace Auth.Controllers { _emailContext = emailContext; } - [Route("login")] - [HttpPost] - public async Task> Login([FromForm] string UserName, [FromForm] string PasswordHash, [FromForm] bool StayLoggedIn) { - try { - Account? test = await _databaseService.GetAccount(UserName.ToLower()); - if (test != null) { - if (test.EmailVerified == true) { - if (test.FailedPasswordLock) { - if (test.CurrentPasswordAttempts >= test.PasswordAttempts) { - return NotFound("Too many failed password attempts. Please reset your password"); - } - } - if (BCrypt.Net.BCrypt.Verify(PasswordHash, test.PasswordHash)) { - test.CurrentPasswordAttempts = 0; - await _databaseService.SetAccount(test); - - string jwt = AuthJWT.GenereateJWTToken(test.ID, StayLoggedIn); - AuthJWT.SignIn(Response, StayLoggedIn, jwt); - - return Ok(test); - } else { - test.CurrentPasswordAttempts += 1; - await _databaseService.SetAccount(test); - return NotFound("Wrong Password"); - } - } else { - await SendVerify(test.UserName); - return NotFound("A new verify email has been sent. \n Note only 1 email send every 5 mintes"); - } - } - return NotFound("Account Not Found"); - } catch (Exception ex) { - Console.WriteLine("Login Error: " + ex.Message); - return NotFound("An internal server error has occured"); - } - } - - [Route("register")] - [HttpPost] - public async Task> Register([FromForm] string Email, [FromForm] string UserName, [FromForm] string PasswordHash) { - try { - if (await _databaseService.GetAccount(UserName.ToLower()) == null) { - if (await _databaseService.GetAccount(Email.ToLower()) == null) { - Account created = new Account() { - UserName = UserName.ToLower(), - Email = Email.ToLower(), - EmailVerified = false, - PasswordHash = BCrypt.Net.BCrypt.HashPassword(PasswordHash), - }; - await _databaseService.SetAccount(created); - Account? loadedAccount = await _databaseService.GetAccount(Email.ToLower()); - if (loadedAccount != null) { - await SendVerify(loadedAccount.UserName); - return Ok(loadedAccount); - } - return NotFound("Unable to create the account"); - } else { - return NotFound("Email is already in use"); - } - } else { - return NotFound("UserName is taken"); - } - } catch (Exception ex) { - Console.WriteLine("Register Error: " + ex.Message); - return NotFound("An internal server error has occured"); - } - - } - - [Route("changepassword")] - [HttpPost] - public async Task ChangePassword([FromForm] string OldPassword, [FromForm] string NewPassword) { - try { - if (isLoggedIn()) { - Account user = await getLoggedInUser(); - if (BCrypt.Net.BCrypt.Verify(OldPassword, user.PasswordHash)) { - user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword); - user.CurrentPasswordAttempts = 0; - await _databaseService.SetAccount(user); - return Ok(); - } - } - return NotFound("Not logged in"); - } catch (Exception ex) { - Console.WriteLine("ChangePassword Error: " + ex.Message); - return NotFound("An internal server error has occured"); - } - } - - [Route("toggleaccountlock")] - [HttpPost] - public async Task> ToggleAccountLock([FromForm] bool AccountLock) { - try { - if (isLoggedIn()) { - Account user = await getLoggedInUser(); - user.FailedPasswordLock = AccountLock; - user.CurrentPasswordAttempts = 0; - await _databaseService.SetAccount(user); - return Ok(); - } - return NotFound("Not logged in"); - } catch (Exception ex) { - Console.WriteLine("ToggleAccountLock Error: " + ex.Message); - return NotFound("An internal server error has occured"); - } - } - [Route("get")] [HttpPost] public async Task> Get() { @@ -129,10 +23,49 @@ namespace Auth.Controllers { if (isLoggedIn()) { return Ok(await getLoggedInUser()); } - return NotFound("Not logged in"); + return BadRequest("Not logged in"); } catch (Exception ex) { Console.WriteLine("Get Error: " + ex); - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); + } + } + + [Route("login")] + [HttpPost] + public async Task> Login([FromBody] LoginRequest request) { + try { + Account? test = await _databaseService.GetAccount(request.UserName.ToLower()); + if (test != null) { + if (test.EmailVerified == true) { + if (test.FailedPasswordLock) { + if (test.CurrentPasswordAttempts >= test.PasswordAttempts) { + return BadRequest("Too many failed password attempts. Please reset your password"); + } + } + if (BCrypt.Net.BCrypt.Verify(request.Password, test.PasswordHash)) { + test.CurrentPasswordAttempts = 0; + await _databaseService.SetAccount(test); + + string jwt = AuthJWT.GenereateJWTToken(test.ID, request.StayLoggedIn); + AuthJWT.SignIn(Response, request.StayLoggedIn, jwt); + + return Ok(test); + } else { + test.CurrentPasswordAttempts += 1; + await _databaseService.SetAccount(test); + return BadRequest("Wrong Password"); + } + } else { + await SendVerify(new SendVerifyEmailRequest { + UserName = test.UserName + }); + return BadRequest("A new verify email has been sent. \n Note only 1 email send every 5 mintes"); + } + } + return BadRequest("Account Not Found"); + } catch (Exception ex) { + Console.WriteLine("Login Error: " + ex.Message); + return BadRequest("An internal server error has occured"); } } @@ -143,81 +76,157 @@ namespace Auth.Controllers { AuthJWT.SignOut(Response); return Ok(); } - return NotFound(); + return BadRequest(); + } + + [Route("register")] + [HttpPost] + public async Task> Register([FromBody] RegisterRequest request) { + try { + if (await _databaseService.GetAccount(request.UserName.ToLower()) == null) { + if (await _databaseService.GetAccount(request.Email.ToLower()) == null) { + Account created = new Account() { + UserName = request.UserName.ToLower(), + Email = request.Email.ToLower(), + EmailVerified = false, + PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password), + }; + await _databaseService.SetAccount(created); + Account? loadedAccount = await _databaseService.GetAccount(request.Email.ToLower()); + if (loadedAccount != null) { + await SendVerify(new SendVerifyEmailRequest { + UserName = loadedAccount.UserName + }); + return Ok(loadedAccount); + } + return BadRequest("Unable to create the account"); + } else { + return BadRequest("Email is already in use"); + } + } else { + return BadRequest("UserName is taken"); + } + } catch (Exception ex) { + Console.WriteLine("Register Error: " + ex.Message); + return BadRequest("An internal server error has occured"); + } + + } + + [Route("changepassword")] + [HttpPost] + public async Task ChangePassword([FromBody] ChangePasswordRequest request) { + try { + if (isLoggedIn()) { + Account user = await getLoggedInUser(); + if (BCrypt.Net.BCrypt.Verify(request.OldPassword, user.PasswordHash)) { + user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword); + user.CurrentPasswordAttempts = 0; + await _databaseService.SetAccount(user); + return Ok(); + } + } + return BadRequest("Not logged in"); + } catch (Exception ex) { + Console.WriteLine("ChangePassword Error: " + ex.Message); + return BadRequest("An internal server error has occured"); + } + } + + [Route("accountlock")] + [HttpPost] + public async Task> setAccountLock([FromBody] AccountLockRequest request) { + try { + if (isLoggedIn()) { + Account user = await getLoggedInUser(); + user.FailedPasswordLock = request.AccountLock; + user.CurrentPasswordAttempts = 0; + await _databaseService.SetAccount(user); + return Ok(); + } + return BadRequest("Not logged in"); + } catch (Exception ex) { + Console.WriteLine("ToggleAccountLock Error: " + ex.Message); + return BadRequest("An internal server error has occured"); + } } [Route("sendverifyemail")] [HttpPost] - public async Task> SendVerify([FromForm] string UserName) { + public async Task> SendVerify([FromBody] SendVerifyEmailRequest request) { try { - string key = "v" + UserName; + string key = "v" + request.UserName.ToLower(); // Stop from sending multiple emails quickly if (_emailContext._SentEmails.ContainsKey(key)) { DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); if (PreviousSentTime.AddMinutes(5) > DateTime.Now) { - return NotFound("Cannot sent another verify email until 5 minutes has elapsed"); - } - else { + return BadRequest("Cannot sent another verify email until 5 minutes has elapsed"); + } else { _emailContext._SentEmails.Remove(key); } } - Account? test = await _databaseService.GetAccount(UserName.ToLower()); + Account? test = await _databaseService.GetAccount(request.UserName.ToLower()); if (test != null) { test.EmailToken = Guid.NewGuid().ToString(); + test.EmailTokenCreated = DateTime.UtcNow; await _databaseService.SetAccount(test); string EmailContents = EmailService.VerifyEmailEmail; - EmailContents = Substitue(EmailContents, "@UserName", UserName); - EmailContents = Substitue(EmailContents, "@UserName", UserName); + EmailContents = Substitue(EmailContents, "@UserName", request.UserName); + EmailContents = Substitue(EmailContents, "@UserName", request.UserName); EmailContents = Substitue(EmailContents, "@VerifyPassword", test.EmailToken); string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents); _emailContext._SentEmails.Add(key, DateTime.Now); return Ok(result); } - return NotFound("Account not found"); + return BadRequest("Account not found"); } catch (Exception) { - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); } } [Route("verifyemail")] [HttpPost] - public async Task> VerifyEmail([FromForm] string UserName, [FromForm] string EmailToken) { + public async Task> VerifyEmail([FromBody] VerifyEmailRequest request) { try { - Account? test = await _databaseService.GetAccount(UserName.ToLower()); + Account? test = await _databaseService.GetAccount(request.UserName.ToLower()); if (test != null) { - if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == EmailToken) { - test.EmailToken = ""; - test.EmailVerified = true; - await _databaseService.SetAccount(test); - return Ok(true); + if (DateTime.UtcNow < test.EmailTokenCreated.AddMinutes(30)) { + if (test.EmailToken == request.EmailToken) { + test.EmailToken = ""; + test.EmailVerified = true; + await _databaseService.SetAccount(test); + return Ok(true); + } + return BadRequest("The token isn't valid"); } + return BadRequest("Your email token has timed out"); } - return NotFound("Account not found or token is invalid");; + return BadRequest("Account not found");; } catch { - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); } } [Route("sendresetpassword")] [HttpPost] - public async Task> ResetPassword([FromForm] string Email) { + public async Task> ResetPassword([FromBody] SendResetPasswordRequest request) { try { - string key = "p" + Email.ToLower(); + string key = "p" + request.Email.ToLower(); // Stop from sending multiple emails quickly if (_emailContext._SentEmails.ContainsKey(key)) { DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); if (PreviousSentTime.AddMinutes(5) > DateTime.Now) { - return NotFound("Cannot sent another reset requests until 5 minutes has elapsed"); - } - else { + return BadRequest("Cannot sent another reset requests until 5 minutes has elapsed"); + } else { _emailContext._SentEmails.Remove(key); } } - Account? test = await _databaseService.GetAccount(Email.ToLower()); + Account? test = await _databaseService.GetAccount(request.Email.ToLower()); if (test != null) { - test.EmailToken = Guid.NewGuid().ToString(); + test.PasswordToken = Guid.NewGuid().ToString(); + test.PasswordTokenCreated = DateTime.UtcNow; await _databaseService.SetAccount(test); string EmailContents = EmailService.ResetPasswordEmail; @@ -229,49 +238,53 @@ namespace Auth.Controllers { _emailContext._SentEmails.Add(key, DateTime.Now); return Ok(result); } - return NotFound("Account Not Found"); + return BadRequest("Account Not Found"); } catch (Exception e) { Console.WriteLine("EmailService Error: " + e.ToString()); - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); } } [Route("resetpassword")] [HttpPost] - public async Task> ResetPwdVerify([FromForm] string UserName, [FromForm] string NewPassword, [FromForm] string ResetToken) { + public async Task> ResetPwdVerify([FromBody] ResetPasswordRequest request) { try { - Account? test = await _databaseService.GetAccount(UserName.ToLower()); - if (test != null && !string.IsNullOrEmpty(test.EmailToken)) { - if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == ResetToken) { - test.CurrentPasswordAttempts = 0; - test.EmailToken = ""; - test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword); - await _databaseService.SetAccount(test); - return Ok(true); + Account? test = await _databaseService.GetAccount(request.UserName.ToLower()); + if (test != null) { + if (DateTime.UtcNow < test.PasswordTokenCreated.AddMinutes(30)) { + if (test.PasswordToken == request.PasswordToken) { + test.CurrentPasswordAttempts = 0; + test.PasswordToken = ""; + test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword); + await _databaseService.SetAccount(test); + return Ok(true); + } + return BadRequest("The token isn't valid"); } + return BadRequest("Your email token has timed out"); } - return NotFound("Account not found or reset token is bad"); + return BadRequest("Account not found");; } catch { - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); } } [Route("delete")] [HttpPost] - public async Task delete([FromForm] string Password) { + public async Task delete([FromBody] DeleteRequest request) { try { if (isLoggedIn()) { Account user = await getLoggedInUser(); - if (BCrypt.Net.BCrypt.Verify(Password, user.PasswordHash)) { + if (BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) { await _databaseService.DeleteAccount(user.ID); return Ok(); } } - return NotFound("User is not logged in"); + return BadRequest("User is not logged in"); } catch (Exception ex) { Console.WriteLine("Delete Error: " + ex.Message); - return NotFound("An internal server error has occured"); + return BadRequest("An internal server error has occured"); } } diff --git a/src/Server/Controllers/OAuth.cs b/src/Server/Controllers/OAuth.cs index 34f22bf..128e897 100755 --- a/src/Server/Controllers/OAuth.cs +++ b/src/Server/Controllers/OAuth.cs @@ -4,15 +4,18 @@ using System.Web.Http; using Auth.Entities; using System.Text; using System.Security.Cryptography; +using Microsoft.IdentityModel.Tokens; namespace Auth.Controllers { [ApiController] [Route("api/oauth/")] public class OAuthController : MistoxControllerBase { - public OAuthController(DatabaseService db) : base(db) {} + public OAuthController(DatabaseService db) : base(db) { } - private string GenerateCodeChallenge(string codeVerifier) { + /* + + private string GenerateCodeChallenge(string codeVerifier) { using var sha256 = SHA256.Create(); var bytes = sha256.ComputeHash(Encoding.ASCII.GetBytes(codeVerifier)); return Base64UrlEncode(bytes); @@ -25,10 +28,16 @@ namespace Auth.Controllers { .Replace('/', '_'); } - [HttpGet("/authorize")] + [HttpGet("authorize")] public async Task Authorize([FromQuery] AuthorizationRequest request) { try { + if (request.ResponseType != "code") { + return BadRequest("unsupported_code_type"); + } + + string RequestingApp = request.ClientId; + // Verify login // create guid // set guid to account @@ -41,7 +50,7 @@ namespace Auth.Controllers { } } - [HttpPost("/token")] + [HttpPost("token")] public async Task Token([FromForm] TokenRequest request) { try { @@ -89,12 +98,74 @@ namespace Auth.Controllers { expires_in = 3600, refresh_token = refreshToken }); - + } catch (Exception ex) { Console.WriteLine("Delete Error: " + ex.Message); return NotFound("An internal server error has occured"); } } + [HttpGet("/userinfo")] + public async Task UserInfo() { + Account user = await getLoggedInUser(); + if (user == null) { + return Unauthorized(); + } + + var claims = new { + sub = user.ID, + preferred_username = user.UserName, + email = user.Email, + email_verified = user.EmailVerified + }; + + return Ok(claims); + } + + [HttpGet("/.well-known/openid-configuration")] + public IActionResult OpenIdConfiguration() { + var issuer = "https://your-auth-server.com"; + + var config = new { + issuer = issuer, + authorization_endpoint = $"{issuer}/authorize", + token_endpoint = $"{issuer}/token", + userinfo_endpoint = $"{issuer}/userinfo", + jwks_uri = $"{issuer}/.well-known/jwks.json", + response_types_supported = new[] { "code", "token", "id_token", "code id_token" }, + subject_types_supported = new[] { "public" }, + id_token_signing_alg_values_supported = new[] { "RS256" }, + scopes_supported = new[] { "openid", "profile", "email" }, + token_endpoint_auth_methods_supported = new[] { "client_secret_basic", "private_key_jwt" }, + claims_supported = new[] { "sub", "name", "preferred_username", "email", "email_verified" } + }; + + return Ok(config); + } + + [HttpGet("/.well-known/jwks.json")] + public IActionResult GetJwks() { + var key = new RsaSecurityKey(rsa) { + KeyId = "my-key-id-123" // a unique key ID, important for clients + }; + + var parameters = key.Rsa.ExportParameters(false); // export public key only + + var jwk = new JsonWebKey { + Kid = key.KeyId, + Kty = "RSA", + Use = "sig", // signing + Alg = SecurityAlgorithms.RsaSha256, + N = Base64UrlEncoder.Encode(parameters.Modulus), + E = Base64UrlEncoder.Encode(parameters.Exponent) + }; + + var jwks = new { keys = new[] { jwk } }; + + return Ok(jwks); + } + + */ + } }