From 8614d18522441543e08c37c68121fed1fa8d6ae7 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sun, 9 Aug 2020 15:51:33 +0200 Subject: auth user --- src/browser/package-lock.json | 31 ++++ src/browser/package.json | 3 +- src/browser/src/api/account.js | 178 ++++++++++++++++++--- src/browser/src/constants.js | 15 +- src/browser/src/main.js | 5 +- src/browser/src/router.js | 27 ++-- src/browser/src/store.js | 14 +- src/browser/src/views/Login.vue | 9 +- src/browser/src/views/OidcCallback.vue | 8 + .../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 + 24 files changed, 537 insertions(+), 98 deletions(-) create mode 100644 src/browser/src/views/OidcCallback.vue 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') diff --git a/src/browser/package-lock.json b/src/browser/package-lock.json index 6e840a1..2e5511f 100644 --- a/src/browser/package-lock.json +++ b/src/browser/package-lock.json @@ -463,6 +463,11 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -721,6 +726,11 @@ } } }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -744,6 +754,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -1963,6 +1978,17 @@ "has": "^1.0.3" } }, + "oidc-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.10.1.tgz", + "integrity": "sha512-/QB5Nl7c9GmT9ir1E+OVY3+yZZnuk7Qa9ZEAJqSvDq0bAyAU9KAgeKipTEfKjGdGLTeOLy9FRWuNpULMkfZydQ==", + "requires": { + "base64-js": "^1.3.0", + "core-js": "^2.6.4", + "crypto-js": "^3.1.9-1", + "uuid": "^3.3.2" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -2743,6 +2769,11 @@ "object.getownpropertydescriptors": "^2.1.0" } }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/src/browser/package.json b/src/browser/package.json index a6d2e0f..baec9c1 100644 --- a/src/browser/package.json +++ b/src/browser/package.json @@ -7,8 +7,9 @@ "build": "vite build" }, "dependencies": { - "vue": "^3.0.0-rc.1", + "oidc-client": "^1.10.1", "vue-router": "^4.0.0-0", + "vue": "^3.0.0-rc.1", "vuex": "^4.0.0-0" }, "devDependencies": { diff --git a/src/browser/src/api/account.js b/src/browser/src/api/account.js index 9ba609b..665eb3f 100644 --- a/src/browser/src/api/account.js +++ b/src/browser/src/api/account.js @@ -1,37 +1,171 @@ import constants from "../constants"; +import Oidc from "oidc-client"; +import store from "../store"; + +const userManager = new Oidc.UserManager({ + authority: constants.api_address, + client_id: "browser", + redirect_uri: `${constants.client_address}/oidc-callback`, + post_logout_redirect_uri: constants.client_address, + response_type: "code", + scope: "openid profile main_api offline_access", + accessTokenExpiringNotificationTime: 10, + automaticSilentRenew: true, + filterProtocolClaims: true, + loadUserInfo: true, + revokeAccessTokenOnSignout: true, +}); + +Oidc.Log.logger = console; +Oidc.Log.level = Oidc.Log.INFO; +let currentUser; +let signedIn = false; + +userManager.events.addUserLoaded(function (user) { + store.commit("setProfileData", user); + console.log("New user:", arguments); + console.log("Access_token: ", user.access_token); +}); + +userManager.events.addAccessTokenExpiring(function () { + console.log("AccessToken Expiring", arguments); +}); + +userManager.events.addAccessTokenExpired(function () { + console.log("AccessToken Expired", arguments); + userManager + .signoutRedirect() + .then(function (resp) { + console.log("signed out", resp); + }) + .catch(function (err) { + console.log(err); + }); +}); + +userManager.events.addSilentRenewError(function () { + console.error("Silent Renew Error:", arguments); +}); + +userManager.events.addUserSignedOut(function () { + alert("Logout"); + console.log("UserSignedOut:", arguments); + //userManager.removeUser(); + userManager + .signoutRedirect() + .then(function (resp) { + console.log("signed out", resp); + }) + .catch(function (err) { + console.log(err); + }); +}); export default { - async loginAsync(username, password) { - let response = await fetch(constants.API_ADDRESS + "/account/login", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - body: JSON.stringify({ - username, - password, - }), + signIn() { + userManager.signinRedirect().catch(function (err) { + console.log(err); }); + }, - return response; + signinRedirectCallback() { + userManager.signinRedirectCallback().then( + () => { + console.log("Logged in"); + }, + (error) => { + console.error(error); + } + ); }, - async getProfileAsync() { - let response = await fetch(constants.API_ADDRESS + "/account/me", { - method: "GET", - credentials: "include", + + signOut() { + var self = this; + userManager + .signoutRedirect() + .then(function (resp) { + self.signedIn = false; + console.log("signed out", resp); + }) + .catch(function (err) { + console.log(err); + }); + }, + + showTokens() { + userManager.getUser().then(function (user) { + if (user) { + console.log("Profile", user.profile); + console.log("Role", user.profile.role); + } else { + self.signIn(); + } }); + }, - return response; + getUser() { + let self = this; + return new Promise((resolve, reject) => { + userManager + .getUser() + .then(function (user) { + if (user == null) { + self.signIn(); + return resolve(null); + } else { + return resolve(user); + } + }) + .catch(function (err) { + console.log(err); + return reject(err); + }); + }); }, - async logoutAsync() { - let response = await fetch(constants.API_ADDRESS + "/account/logout", { - method: "GET", - credentials: "include", + + getSignedIn() { + let self = this; + return new Promise((resolve, reject) => { + userManager + .getUser() + .then(function (user) { + if (user == null) { + self.signIn(); + return resolve(false); + } else { + currentUser = user; + signedIn = true; + return resolve(signedIn); + } + }) + .catch(function (err) { + console.log(err); + return reject(err); + }); }); + }, - return response; + getRole() { + let self = this; + return new Promise((resolve, reject) => { + userManager + .getUser() + .then(function (user) { + if (user == null) { + self.signIn(); + return resolve(false); + } else { + currentUser = user; + return resolve(user.profile.role); + } + }) + .catch(function (err) { + console.log(err); + return reject(err); + }); + }); }, + async sendResetPasswordMailAsync(username) { let response = await fetch(constants.API_ADDRESS + "/account/forgot", { method: "GET", diff --git a/src/browser/src/constants.js b/src/browser/src/constants.js index ddeebe8..471cc92 100644 --- a/src/browser/src/constants.js +++ b/src/browser/src/constants.js @@ -1,13 +1,12 @@ -const constants = { - API_ADDRESS: "http://localhost:5001/api", +export default { + api_address: "http://localhost:5001", + client_address: "http://localhost:3000", storageKeys: { - COOKIE_LAST_SEEN: "cookie-last-seen", + cookie_last_seen: "cookie-last-seen", }, types: { - SET_PROFILE_DATA: "set-profile-data", - LOGIN_ASYNC: "login-async", - DONWLOAD_PROFILE_DATA: "download-profile-data-async", + set_profile_data: "set-profile-data", + login_async: "login-async", + download_profile_data: "download-profile-data-async", }, }; - -export default constants; diff --git a/src/browser/src/main.js b/src/browser/src/main.js index db7debc..89ba0ea 100644 --- a/src/browser/src/main.js +++ b/src/browser/src/main.js @@ -3,7 +3,4 @@ import App from "./App.vue"; import router from "./router"; import store from "./store"; -createApp(App) - .use(store) - .use(router) - .mount("#app"); +createApp(App).use(store).use(router).mount("#app"); diff --git a/src/browser/src/router.js b/src/browser/src/router.js index 4b25557..0fd5462 100644 --- a/src/browser/src/router.js +++ b/src/browser/src/router.js @@ -1,4 +1,5 @@ import { createRouter, createWebHistory } from "vue-router"; + import store from "./store"; import Home from "./views/Home.vue"; @@ -9,48 +10,54 @@ import Privacy from "./views/Privacy.vue"; import Transactions from "./views/Transactions.vue"; import Settings from "./views/Settings.vue"; import Account from "./views/Account.vue"; +import OidcCallback from "./views/OidcCallback.vue"; const routes = [ { path: "/", - allowUnauthenticated: false, + isPublic: false, component: Home, }, { path: "/transactions", - allowUnauthenticated: false, + isPublic: false, component: Transactions, }, { path: "/settings", - allowUnauthenticated: false, + isPublic: false, component: Settings, }, { path: "/account", - allowUnauthenticated: false, + isPublic: false, component: Account, }, { path: "/login", - allowUnauthenticated: true, + isPublic: true, component: Login, }, { path: "/forgot", - allowUnauthenticated: true, + isPublic: true, component: Forgot, }, { path: "/signup", - allowUnauthenticated: true, + isPublic: true, component: Signup, }, { path: "/privacy", - allowUnauthenticated: true, + isPublic: true, component: Privacy, }, + { + path: "/oidc-callback", + isPublic: true, + component: OidcCallback, + }, ]; const router = createRouter({ @@ -60,8 +67,8 @@ const router = createRouter({ router.beforeEach((to, from, next) => { console.log("store.state.profile.isAuthenticated: " + store.state.profile.isAuthenticated); - const unRestrictedPaths = routes.filter((r) => r.allowUnauthenticated); - if (unRestrictedPaths.every((c) => c.path !== to.path) && !store.state.profile.isAuthenticated) + const publicPaths = routes.filter((r) => r.isPublic); + if (publicPaths.every((c) => c.path !== to.path) && !store.state.profile.isAuthenticated) next("/login"); else next(); }); diff --git a/src/browser/src/store.js b/src/browser/src/store.js index 0dd3195..c775ed8 100644 --- a/src/browser/src/store.js +++ b/src/browser/src/store.js @@ -3,21 +3,11 @@ import { createStore } from "vuex"; export default createStore({ strict: true, state: { - profile: { - id: "", - username: "", - sessionStart: "", - isAuthenticated: false, - }, + profile: {}, }, mutations: { setProfileData(state, profile) { - state.profile = { - id: profile.id, - username: profile.username, - sessionStart: profile.sessionStart, - isAuthenticated: profile.id !== "", - }; + state.profile = profile; }, }, actions: {}, diff --git a/src/browser/src/views/Login.vue b/src/browser/src/views/Login.vue index 07b5b31..a94ea9e 100644 --- a/src/browser/src/views/Login.vue +++ b/src/browser/src/views/Login.vue @@ -70,6 +70,7 @@ import store from "../store"; import router from "../router"; import account from "../api/account"; + export default { components: { Alert, @@ -89,6 +90,12 @@ export default { }, }); + account.signIn() + .then((user) => console.log(user)) + .catch((err) => console.error(err)); + + function submitForm() {} + /* async function submitForm() { model.isLoading = true; return; @@ -122,7 +129,7 @@ export default { } } } - +*/ const forgotPassword = () => router.replace("/forgot"); const signup = () => router.replace("/signup"); diff --git a/src/browser/src/views/OidcCallback.vue b/src/browser/src/views/OidcCallback.vue new file mode 100644 index 0000000..10ccd52 --- /dev/null +++ b/src/browser/src/views/OidcCallback.vue @@ -0,0 +1,8 @@ + + + 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