From 8614d18522441543e08c37c68121fed1fa8d6ae7 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sun, 9 Aug 2020 15:51:33 +0200 Subject: auth user --- .../key-75aa5a6c-406f-4e39-b1df-58a8d3e6af7a.xml | 16 ++++ src/server/Controllers/AccountController.cs | 71 ++++++++++++---- src/server/Controllers/BaseController.cs | 2 +- src/server/Dough.csproj | 11 +++ src/server/IdentityServer/Config.cs | 8 +- src/server/Models/Constants.cs | 8 +- src/server/Models/Payloads/LoginPayload.cs | 2 + src/server/Models/Results/ErrorResult.cs | 8 +- src/server/Pages/Error.cshtml | 19 +++++ src/server/Pages/Error.cshtml.cs | 12 +++ src/server/Pages/Login.cshtml | 95 ++++++++++++++++++++++ src/server/Pages/Login.cshtml.cs | 12 +++ src/server/Services/EmailService.cs | 35 +++++++- src/server/Startup.cs | 45 ++++++---- src/server/tempkey.jwk | 1 + 15 files changed, 305 insertions(+), 40 deletions(-) create mode 100644 src/server/AppData/dpkeys/key-75aa5a6c-406f-4e39-b1df-58a8d3e6af7a.xml create mode 100644 src/server/Pages/Error.cshtml create mode 100644 src/server/Pages/Error.cshtml.cs create mode 100644 src/server/Pages/Login.cshtml create mode 100644 src/server/Pages/Login.cshtml.cs create mode 100644 src/server/tempkey.jwk (limited to 'src/server') diff --git a/src/server/AppData/dpkeys/key-75aa5a6c-406f-4e39-b1df-58a8d3e6af7a.xml b/src/server/AppData/dpkeys/key-75aa5a6c-406f-4e39-b1df-58a8d3e6af7a.xml new file mode 100644 index 0000000..51115c2 --- /dev/null +++ b/src/server/AppData/dpkeys/key-75aa5a6c-406f-4e39-b1df-58a8d3e6af7a.xml @@ -0,0 +1,16 @@ + + + 2020-08-09T12:29:14.3954895Z + 2020-08-09T12:29:14.3177582Z + 2020-11-07T12:29:14.3177582Z + + + + + + + iO4MQasV7DgDGtCJg0PE5yYk/skUTg3Hvzk1gxTzq2sVxDSysKKKJGYuT0k5yctxWlGnOACrGeKyBhTelz3hbA== + + + + \ No newline at end of file diff --git a/src/server/Controllers/AccountController.cs b/src/server/Controllers/AccountController.cs index fe7b7a2..5c760e2 100644 --- a/src/server/Controllers/AccountController.cs +++ b/src/server/Controllers/AccountController.cs @@ -1,11 +1,20 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Dough.Models; using Dough.Models.Database; +using Dough.Models.Payloads; +using Dough.Models.Results; +using Dough.Services; using Dough.Utilities; +using IdentityServer4; using IdentityServer4.Services; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; namespace Dough.Controllers { @@ -13,34 +22,68 @@ namespace Dough.Controllers public class AccountController : BaseController { private readonly MainDbContext _context; - private readonly IIdentityServerInteractionService _identityServerInteractionService; + private readonly IIdentityServerInteractionService _interaction; + private readonly EmailService _emailService; public AccountController(MainDbContext context, - IIdentityServerInteractionService identityServerInteractionService) + IIdentityServerInteractionService interaction, + EmailService emailService) { _context = context; - _identityServerInteractionService = identityServerInteractionService; + _interaction = interaction; + _emailService = emailService; } + [HttpGet("login")] + public ActionResult GetLogin() + { + var pathToLoginFile = Path.Combine(Directory.GetCurrentDirectory(), "AppData", "login.html"); + var fileContent = System.IO.File.ReadAllText(pathToLoginFile); + return Content(fileContent, "text/html"); + } - // This is the default route for identityserver4 logins (https://identityserver4.readthedocs.io/en/latest/topics/signin.html#login-workflow) [HttpPost("login")] - public async Task Login(string returnUrl) + [ValidateAntiForgeryToken] + public async Task PostLogin(LoginPayload payload) { - if (returnUrl.IsMissing() || !_identityServerInteractionService.IsValidReturnUrl(returnUrl)) - return BadRequest("route parameter returnUrl is invalid"); + if (!_interaction.IsValidReturnUrl(payload.ReturnUrl)) + return BadRequest(new ErrorResult()); + var user = _context.Users.SingleByNameOrDefault(payload.Username); + if (user == default) + { + await Task.Delay(1500); + return BadRequest(new ErrorResult("Username or password is incorrect","Please try again with a different username and/or password")); + } - Console.WriteLine("returnUrl: " + returnUrl); - var reqBody = await HttpContext.Request.ReadFormAsync(); - foreach (var formEl in reqBody) + if (!user.VerifyPassword(payload.Password)) { - Console.WriteLine(formEl.Key); - foreach (var value in formEl.Value) - Console.WriteLine(" - " + value); + await Task.Delay(1000); + return BadRequest(new ErrorResult("Username or password is incorrect","Please try again with a different username and/or password")); } - return Ok(); + + var props = new AuthenticationProperties + { + AllowRefresh = true, + IssuedUtc = DateTime.UtcNow, + }; + + if (payload.Persist) + { + props.IsPersistent = true; + props.ExpiresUtc = DateTime.UtcNow.AddDays(15); + } + + var identityServerUser = new IdentityServerUser(user.Id.ToString()) + { + DisplayName = user.Username, + AuthenticationTime = DateTime.UtcNow, + }; + + await HttpContext.SignInAsync(identityServerUser, props); + + return Ok(payload.ReturnUrl); } diff --git a/src/server/Controllers/BaseController.cs b/src/server/Controllers/BaseController.cs index 046c060..44b11b2 100644 --- a/src/server/Controllers/BaseController.cs +++ b/src/server/Controllers/BaseController.cs @@ -6,7 +6,7 @@ using Dough.Utilities; namespace Dough.Controllers { [ApiController] - [Route("api/[controller]")] + [Route("[controller]")] public class BaseController : ControllerBase { public LoggedInUserModel LoggedInUser => new LoggedInUserModel diff --git a/src/server/Dough.csproj b/src/server/Dough.csproj index e54ff49..407dc84 100644 --- a/src/server/Dough.csproj +++ b/src/server/Dough.csproj @@ -8,12 +8,23 @@ + + + + true + PreserveNewest + + + true + PreserveNewest + + diff --git a/src/server/IdentityServer/Config.cs b/src/server/IdentityServer/Config.cs index 41363f1..5b2bf13 100644 --- a/src/server/IdentityServer/Config.cs +++ b/src/server/IdentityServer/Config.cs @@ -22,11 +22,17 @@ namespace Dough.IdentityServer RedirectUris = Constants.BrowserAppLoginRedirectUrls, PostLogoutRedirectUris = Constants.BrowserAppLogoutRedirectUrls, AllowedCorsOrigins = Constants.BrowserAppUrls, + AccessTokenType = AccessTokenType.Reference, + RequireConsent = false, + RefreshTokenExpiration = TokenExpiration.Sliding, + AlwaysSendClientClaims = true, + AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.OfflineAccess, MainApiScopeName } } @@ -45,4 +51,4 @@ namespace Dough.IdentityServer new IdentityResources.Profile(), }; } -} +} \ No newline at end of file diff --git a/src/server/Models/Constants.cs b/src/server/Models/Constants.cs index 3afaaad..04e8a2b 100644 --- a/src/server/Models/Constants.cs +++ b/src/server/Models/Constants.cs @@ -11,13 +11,13 @@ namespace Dough.Models }; public static readonly string[] BrowserAppLoginRedirectUrls = { - "http://localhost:8080/signin-oidc", - "http://localhost:3000/signin-oidc", + "http://localhost:8080/oidc-callback", + "http://localhost:3000/oidc-callback", }; public static readonly string[] BrowserAppLogoutRedirectUrls = { - "http://localhost:8080/signout-callback-oidc", - "http://localhost:3000/signout-callback-oidc", + "http://localhost:8080", + "http://localhost:3000", }; } diff --git a/src/server/Models/Payloads/LoginPayload.cs b/src/server/Models/Payloads/LoginPayload.cs index d7bc50b..1a112a6 100644 --- a/src/server/Models/Payloads/LoginPayload.cs +++ b/src/server/Models/Payloads/LoginPayload.cs @@ -4,5 +4,7 @@ namespace Dough.Models.Payloads { public string Username { get; set; } public string Password { get; set; } + public string ReturnUrl { get; set; } + public bool Persist { get; set; } } } \ No newline at end of file diff --git a/src/server/Models/Results/ErrorResult.cs b/src/server/Models/Results/ErrorResult.cs index 8d4504f..edd447c 100644 --- a/src/server/Models/Results/ErrorResult.cs +++ b/src/server/Models/Results/ErrorResult.cs @@ -2,13 +2,13 @@ namespace Dough.Models.Results { public class ErrorResult { - public ErrorResult(string title = default, string message = default) + public ErrorResult(string title = "Something went wrong", string message = "Please try again soon") { Title = title; Message = message; } - public string Title { get; set; } = "En feil oppstod"; - public string Message { get; set; } = "Vennligst prøv igjen snart"; + public string Title { get; set; } + public string Message { get; set; } } -} +} \ No newline at end of file diff --git a/src/server/Pages/Error.cshtml b/src/server/Pages/Error.cshtml new file mode 100644 index 0000000..6030139 --- /dev/null +++ b/src/server/Pages/Error.cshtml @@ -0,0 +1,19 @@ +@page +@model Dough.Pages.Error + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/src/server/Pages/Error.cshtml.cs b/src/server/Pages/Error.cshtml.cs new file mode 100644 index 0000000..5ec2c40 --- /dev/null +++ b/src/server/Pages/Error.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Dough.Pages +{ + public class Error : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/src/server/Pages/Login.cshtml b/src/server/Pages/Login.cshtml new file mode 100644 index 0000000..d3829a2 --- /dev/null +++ b/src/server/Pages/Login.cshtml @@ -0,0 +1,95 @@ +@page +@model Dough.Pages.Login + +@{ + Layout = null; +} + + + + + + Login - dough + + + + +
+ + + + + + @Html.AntiForgeryToken() + +
+ + + + + \ No newline at end of file diff --git a/src/server/Pages/Login.cshtml.cs b/src/server/Pages/Login.cshtml.cs new file mode 100644 index 0000000..02d5ee1 --- /dev/null +++ b/src/server/Pages/Login.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Dough.Pages +{ + public class Login : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/src/server/Services/EmailService.cs b/src/server/Services/EmailService.cs index 0d70f0f..9d795d6 100644 --- a/src/server/Services/EmailService.cs +++ b/src/server/Services/EmailService.cs @@ -1,7 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + namespace Dough.Services { public class EmailService { - + private readonly IConfiguration _configuration; + + public EmailService(IConfiguration configuration) + { + _configuration = configuration; + } + + public async Task Send(string subject, string email) + { + var password = _configuration.GetValue(""); + var emailUser = _configuration.GetValue(""); + var emailHost = _configuration.GetValue(""); + + var httpClient = new HttpClient(); + + var payload = new FormUrlEncodedContent(new[] + { + new KeyValuePair("username", emailUser), + new KeyValuePair("password", password), + }); + + var requestUri = new Uri(emailHost); + var request = await httpClient.PostAsync(requestUri, payload); + + return request.IsSuccessStatusCode; + } } -} \ No newline at end of file +} diff --git a/src/server/Startup.cs b/src/server/Startup.cs index f55a761..891965b 100644 --- a/src/server/Startup.cs +++ b/src/server/Startup.cs @@ -1,3 +1,4 @@ +using System.IO; using Dough.IdentityServer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -7,6 +8,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Dough.Models; using Dough.Models.Database; +using Dough.Services; +using IdentityServer4.Configuration; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,7 +24,7 @@ namespace Dough } public IConfiguration Configuration { get; } - + private const string DefaultCorsPolicy = "DefaultCorsPolicy"; private string GetConnectionStringFromEnvironment() @@ -35,7 +39,6 @@ namespace Dough public void ConfigureServices(IServiceCollection services) { - services.AddCors(options => { options.AddPolicy(DefaultCorsPolicy, builder => @@ -48,25 +51,34 @@ namespace Dough }); }); + var dataprotectionkeyPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData", "dpkeys"); + services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(dataprotectionkeyPath)); + services.AddHealthChecks() .AddDbContextCheck(); - services.AddDbContext(options => { - options.UseMySql(GetConnectionStringFromEnvironment()); - }); - - services.Configure(options => + services.AddDbContext(options => { - options.SuppressModelStateInvalidFilter = true; - options.SuppressInferBindingSourcesForParameters = true; + options.UseMySql(GetConnectionStringFromEnvironment()); }); - var builder = services.AddIdentityServer() + + services.AddIdentityServer(options => + { + options.UserInteraction = new UserInteractionOptions + { + LoginUrl = "/login", + ErrorUrl = "/error", + }; + }) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) + .AddDeveloperSigningCredential() .AddInMemoryClients(Config.Clients); - + services.AddSingleton(); + services.AddControllers(); + services.AddRazorPages().AddRazorRuntimeCompilation(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -78,9 +90,14 @@ namespace Dough app.UseCors(DefaultCorsPolicy); app.UseHealthChecks("/health"); app.UseStatusCodePages(); - app.UseAuthentication(); + app.UseIdentityServer(); app.UseAuthorization(); - app.UseEndpoints(endpoints => { endpoints.MapControllers().RequireAuthorization(); }); + app.UseEndpoints(endpoints => + { + endpoints.MapRazorPages(); + endpoints.MapControllers() + .RequireAuthorization(); + }); } } -} +} \ No newline at end of file diff --git a/src/server/tempkey.jwk b/src/server/tempkey.jwk new file mode 100644 index 0000000..43ef569 --- /dev/null +++ b/src/server/tempkey.jwk @@ -0,0 +1 @@ +{"alg":"RS256","d":"wWiBhJo4kDImxwFXqpjJNTRgkb8-f0daqMsVghuMyh9U93JI0bcEHvym4tG9yEAEd4dQXXvXG9SMkx-lQ41GKtkd6U6ZDjFa0QQuH-rGt4Q0MCwYAhFIpzeMZwoVtz0811cWg2QattvZW7zaz5SkjX-CaQ5VJFSLHtMlsPzkE5xxqRPddGK83qXkyEAa0EzmOnCqlwVRtXrP2AgvqhkUjJM-vg0wjp_Bd204C_ECvi09EAB04jor2w3a4zXMIqsJFi2LQwy-1tG6HY8msgKeL-zfkE-ilCCLtWbDnsMEmFdFWksnBnqY-T855O7EjoaI7NXnUW6MvX_EEg_Mn21ioQ","dp":"etVHep7VLlt0AMz0dihBayeYUEa3FzpiIB8AqkB94lMWeM5u1xlrRrMt5UzKmXnT80Bn3HGMPxZ22gIbpmBZceQ2rG2fhQwR86en0a5LW2eL9IltmfOILsMYSDGTcUSPYfXjJjxzjhtbVufNtm4UsjgfEUrozAp4vntkWpxBYzE","dq":"gbQ-JWMX1OMpJY8AkzVvpMAysyqO3pb33UqTv6D9Q7vhPzagwpAZDhXu9B2PW3cRUWkjyiSKy7VzlUvNo-t88-lFbKk40k0xs6OSQvS5cWr_gChdytvhZw8HFzwej8oJ2ZKQGI2_nExgxmtlD7JSSI56VnG2JK37GHQXunsCxWE","e":"AQAB","kid":"E8C61E059FD40310181EDCDF860FEFFC","kty":"RSA","n":"zds1h4_ELIOkVouKRe7FJR4IlxHajw_3BSvFv6lRWsLJvP42nT1dkAffvB4JeJHNObtEITY1kKy5mAcWDo_o82ZFkzTAzKnme3F5U9RKJdimuKYBX2fX2zbjFQobKbaG-YWsrOaFzclvw0qn-APgHJ8E-7AAE90bbeDNs7GgGDkYb8pWsdz6xVcGJ4z1gYo-68bCyg4ki60s69b69d-IYlvElvKXX0wRKFokdsfzntHVlb-oXWH58peffOZEDLHlwVgojvUcItXXKTb0_tXTh4XD-Eh3xL7S_s1vPjxK0c8wk_55nQ5ib5EP16rZtCV4ZWpLPk2DSL4prnHDdE2J_w","p":"68iIC3HGTbcL1XpdB5vDJBp6sqhIU4KFooWulIA_mt96rtzFc4wv7cDH0BeiHGBBmff0lEr8c178RP1brOQe6LF_yZDoZqkt9hWmivDJYbElLRqasjF5M5EFYNgnAuydY5G58E-c-_zkQk6GUpj0YVTX1fcq_Hhs4Sdi4ol8Lek","q":"34HGqU-SF-AboSwXD_rZgy0ReEf03GQYee5zvLhcAa1SJCHRNoBY8mxTNgqT7mFyHC4FuunY6DNChcH8Ooq4zpuwBQ1_zu4Kj2HwGsGeQDfm36K4FDRslIT-24MAMBdvfhdT-OCbXohTs2Z_sp1A6GtJl2XcLehOBGkB7dE9f6c","qi":"GxlYgDzfeRT_b99IXLOv4BbVmRe1CmuuJkoY5iP0EENscaaE5Qz5iAh_DDs72TPE7Hwdp957QRkWWr8yDC_T0-ZVpVIBGNdUtl3rB18USmSFYn4aY57cFzY5Qs9O7jKrGp8jbZNiD5BVSExhEKxNUdBbsadEWWJrLKl-YB4yShw"} \ No newline at end of file -- cgit v1.3