From 43bdccc5fa91685b49b9134f16bb15631156ce2b Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 23 Nov 2023 22:50:07 +0100 Subject: [PATCH] Login and auth and api swagger improvements --- TIAMMobileApp/MauiProgram.cs | 7 +- .../Services/SecureStorageHandler.cs | 25 +++ TIAMMobileApp/Services/UserDataService.cs | 90 ++++++-- TIAMSharedUI/Pages/Components/AppLaunch.razor | 56 +++++ TIAMSharedUI/Pages/Components/Step2.razor | 2 +- TIAMSharedUI/Pages/Index.razor | 2 +- TIAMSharedUI/Pages/Login.razor | 2 +- TIAMSharedUI/Pages/Login2.razor | 112 ++++++---- TIAMSharedUI/Pages/Register2.razor | 35 ---- TIAMSharedUI/Pages/Register2.razor.css | 3 - TIAMSharedUI/Pages/Register3.razor | 35 ---- TIAMSharedUI/Pages/Register3.razor.css | 3 - TIAMSharedUI/Pages/Register4.razor | 50 ----- TIAMSharedUI/Pages/Register4.razor.css | 65 ------ TIAMSharedUI/Shared/AdminLayout.razor | 13 +- TIAMSharedUI/Shared/MainLayout.razor | 18 +- TIAMSharedUI/Shared/NavMenu.razor | 13 +- TIAMWebApp/Client/Program.cs | 13 +- .../Client/Services/LocalStorageAccessor.cs | 31 +++ .../Client/Services/SecureStorageHandler.cs | 33 +++ .../Client/Services/SessionStorageAccessor.cs | 58 ++++++ TIAMWebApp/Client/Services/UserDataService.cs | 90 ++++++-- TIAMWebApp/Client/TIAMWebApp.Client.csproj | 1 + .../Client/wwwroot/js/LocalStorageAccessor.js | 15 ++ .../wwwroot/js/SessionStorageAccessor.js | 15 ++ .../Server/Controllers/UserAPIController.cs | 197 +++++++++++++++++- TIAMWebApp/Server/Models/AuthenticateUser.cs | 10 + .../Server/Models/RefreshTokenRequest.cs | 8 + TIAMWebApp/Server/Program.cs | 79 +++++++ TIAMWebApp/Server/TIAMWebApp.Server.csproj | 5 + .../Server/appsettings.Development.json | 7 +- TIAMWebApp/Server/appsettings.json | 7 +- .../Interfaces/ISecureStorageHandler.cs | 14 ++ .../Shared/Interfaces/IUserDataService.cs | 2 + TIAMWebApp/Shared/Models/APIUrls.cs | 7 +- .../Models/AuthenticateRequestAndResponse.cs | 9 + .../Shared/Models/AuthenticationResponse.cs | 9 + .../Shared/Models/ClientSide/Setting.cs | 13 ++ .../Models/ClientSide/UserBasicDetails.cs | 24 +++ TIAMWebApp/Shared/Models/ErrorResponse.cs | 16 ++ TIAMWebApp/Shared/Models/MainResponse.cs | 9 + TIAMWebApp/Shared/Models/User.cs | 20 +- .../TIAMWebApp.Shared.Application.csproj | 1 + .../Shared/Utility/LogToBrowserConsole.cs | 15 +- 44 files changed, 904 insertions(+), 335 deletions(-) create mode 100644 TIAMMobileApp/Services/SecureStorageHandler.cs create mode 100644 TIAMSharedUI/Pages/Components/AppLaunch.razor delete mode 100644 TIAMSharedUI/Pages/Register2.razor delete mode 100644 TIAMSharedUI/Pages/Register2.razor.css delete mode 100644 TIAMSharedUI/Pages/Register3.razor delete mode 100644 TIAMSharedUI/Pages/Register3.razor.css delete mode 100644 TIAMSharedUI/Pages/Register4.razor delete mode 100644 TIAMSharedUI/Pages/Register4.razor.css create mode 100644 TIAMWebApp/Client/Services/LocalStorageAccessor.cs create mode 100644 TIAMWebApp/Client/Services/SecureStorageHandler.cs create mode 100644 TIAMWebApp/Client/Services/SessionStorageAccessor.cs create mode 100644 TIAMWebApp/Client/wwwroot/js/LocalStorageAccessor.js create mode 100644 TIAMWebApp/Client/wwwroot/js/SessionStorageAccessor.js create mode 100644 TIAMWebApp/Server/Models/AuthenticateUser.cs create mode 100644 TIAMWebApp/Server/Models/RefreshTokenRequest.cs create mode 100644 TIAMWebApp/Shared/Interfaces/ISecureStorageHandler.cs create mode 100644 TIAMWebApp/Shared/Models/AuthenticateRequestAndResponse.cs create mode 100644 TIAMWebApp/Shared/Models/AuthenticationResponse.cs create mode 100644 TIAMWebApp/Shared/Models/ClientSide/Setting.cs create mode 100644 TIAMWebApp/Shared/Models/ClientSide/UserBasicDetails.cs create mode 100644 TIAMWebApp/Shared/Models/ErrorResponse.cs create mode 100644 TIAMWebApp/Shared/Models/MainResponse.cs diff --git a/TIAMMobileApp/MauiProgram.cs b/TIAMMobileApp/MauiProgram.cs index ccf085c1..821d1efb 100644 --- a/TIAMMobileApp/MauiProgram.cs +++ b/TIAMMobileApp/MauiProgram.cs @@ -3,6 +3,7 @@ using TIAMMobileApp.Services; using TIAMWebApp.Shared.Application.Interfaces; using DevExpress.Blazor; using TIAMMobilApp.Services; +using TIAMWebApp.Shared.Application.Utility; namespace TIAMMobileApp { @@ -35,7 +36,7 @@ namespace TIAMMobileApp /*Android*/ //client.BaseAddress = new Uri("https://10.0.2.2:7116"); - builder.Services.AddScoped(sp => client); + builder.Services.AddSingleton(sp => client); builder.Services.AddDevExpressBlazor(configure => configure.BootstrapVersion = BootstrapVersion.v5); @@ -43,7 +44,9 @@ namespace TIAMMobileApp builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); return builder.Build(); } diff --git a/TIAMMobileApp/Services/SecureStorageHandler.cs b/TIAMMobileApp/Services/SecureStorageHandler.cs new file mode 100644 index 00000000..ae3afd53 --- /dev/null +++ b/TIAMMobileApp/Services/SecureStorageHandler.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TIAMWebApp.Shared.Application.Interfaces; +using TIAMWebApp.Shared.Application.Models.ClientSide; + +namespace TIAMMobileApp.Services +{ + public class SecureStorageHandler : ISecureStorageHandler + { + public async Task SaveToSecureStorageAsync(string key, string value) + { + + await SecureStorage.SetAsync(key, value); + + } + + public async Task GetFromSecureStorageAsync(string key) + { + return await SecureStorage.GetAsync(key); + } + } +} diff --git a/TIAMMobileApp/Services/UserDataService.cs b/TIAMMobileApp/Services/UserDataService.cs index 650ed94d..f4f62064 100644 --- a/TIAMMobileApp/Services/UserDataService.cs +++ b/TIAMMobileApp/Services/UserDataService.cs @@ -4,6 +4,7 @@ using System.Net.Http.Json; using System.Text; using TIAMWebApp.Shared.Application.Interfaces; using TIAMWebApp.Shared.Application.Models; +using TIAMWebApp.Shared.Application.Models.ClientSide; using TIAMWebApp.Shared.Application.Models.PageModels; @@ -12,12 +13,14 @@ namespace TIAMMobilApp.Services public class UserDataService : IUserDataService { private readonly HttpClient http; - public User? User { get; set; } = new User("","",""); + private readonly ISecureStorageHandler secureStorageHandler; + public User? User { get; set; } = new User("", "", ""); public Dictionary userRoleTypes { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public UserDataService(HttpClient http) + public UserDataService(HttpClient http, ISecureStorageHandler secureStorageHandler) { this.http = http; + this.secureStorageHandler = secureStorageHandler; } @@ -40,7 +43,7 @@ namespace TIAMMobilApp.Services { if (User == null) { - User = new User("","",""); + User = new User("", "", ""); User.IsLoggedIn = false; User.UserType = UserType.User; return User; @@ -50,7 +53,7 @@ namespace TIAMMobilApp.Services { return User; } - + } //Mock method for now @@ -66,7 +69,8 @@ namespace TIAMMobilApp.Services return User; } - public async Task TestUserApi(int Param) { + public async Task TestUserApi(int Param) + { var url = APIUrls.UserTest; var response = await http.PostAsJsonAsync(url, Param); var result = await response.Content.ReadAsStringAsync(); @@ -75,15 +79,15 @@ namespace TIAMMobilApp.Services public async Task AuthenticateUser(LoginModel loginModel) { - - - string result = string.Empty; - var url = APIUrls.AuthenticateUser; - + + + string result = string.Empty; + var url = APIUrls.AuthenticateUser; + var response = await http.PostAsJsonAsync(url, loginModel); - if(response.IsSuccessStatusCode) + if (response.IsSuccessStatusCode) { result = await response.Content.ReadAsStringAsync(); } @@ -95,7 +99,7 @@ namespace TIAMMobilApp.Services //result = await response.Content.ReadAsStringAsync(); return result; - + } public async Task<(bool isSuccess, string ErrorMessage)> CreateUser(RegistrationModel regModel) @@ -105,7 +109,7 @@ namespace TIAMMobilApp.Services string result = string.Empty; var url = APIUrls.CreateUser; - var response = await http.PostAsJsonAsync(url, regModel); + var response = await http.PostAsJsonAsync(url, regModel); result = await response.Content.ReadAsStringAsync(); /*if (response.IsSuccessStatusCode) { @@ -122,25 +126,67 @@ namespace TIAMMobilApp.Services return (isSuccess, result); } + public async Task RefreshToken() + { + bool isTokenRefreshed = false; + using (var client = new HttpClient()) + { + var url = APIUrls.RefreshToken; + + var serializedStr = JsonConvert.SerializeObject(new AuthenticateRequestAndResponse + { + RefreshToken = Setting.UserBasicDetails.RefreshToken, + AccessToken = Setting.UserBasicDetails.AccessToken + }); + + try + { + var response = await client.PostAsync(url, new StringContent(serializedStr, Encoding.UTF8, "application/json")); + if (response.IsSuccessStatusCode) + { + string contentStr = await response.Content.ReadAsStringAsync(); + var mainResponse = JsonConvert.DeserializeObject(contentStr); + if (mainResponse.IsSuccess) + { + var tokenDetails = JsonConvert.DeserializeObject(mainResponse.Content.ToString()); + Setting.UserBasicDetails.AccessToken = tokenDetails.AccessToken; + Setting.UserBasicDetails.RefreshToken = tokenDetails.RefreshToken; + + string userDetailsStr = JsonConvert.SerializeObject(Setting.UserBasicDetails); + await secureStorageHandler.SaveToSecureStorageAsync(nameof(Setting.UserBasicDetails), userDetailsStr); + isTokenRefreshed = true; + } + } + } + catch (Exception ex) + { + string msg = ex.Message; + } + + + } + return isTokenRefreshed; + } + public Task> GetUserRolesAsync(User user) { //get the user's roles - int role = User.UserRoles; + int role = User.UserRoles; - foreach (var roleType in roleTypes) + foreach (var roleType in roleTypes) + { + if ((role & roleType.Id) == roleType.Id) { - if ((role & roleType.Id) == roleType.Id) - { - - //add the role to the dictionary - userRoleTypes.Add(roleType.Id, roleType.RoleName); - } + //add the role to the dictionary + userRoleTypes.Add(roleType.Id, roleType.RoleName); + } + } return Task.FromResult(userRoleTypes); - + } } } diff --git a/TIAMSharedUI/Pages/Components/AppLaunch.razor b/TIAMSharedUI/Pages/Components/AppLaunch.razor new file mode 100644 index 00000000..ec1d3b5d --- /dev/null +++ b/TIAMSharedUI/Pages/Components/AppLaunch.razor @@ -0,0 +1,56 @@ +@page "/"; +@using TIAMWebApp.Shared.Application.Interfaces +@using TIAMWebApp.Shared.Application.Models +@using TIAMWebApp.Shared.Application.Utility +@using Newtonsoft.Json +@using System.IdentityModel.Tokens.Jwt +@using TIAMWebApp.Shared.Application.Models.ClientSide +@inject NavigationManager NavManager +@inject LogToBrowserConsole logToBrowserConsole +@inject IUserDataService UserDataService +@inject ISecureStorageHandler SecureStorageHandler +

AppLaunch

+ +Loading.... + +@code { + + + protected async override Task OnInitializedAsync() + { + string userDetailsStr = await SecureStorageHandler.GetFromSecureStorageAsync(nameof(Setting.UserBasicDetails)); + logToBrowserConsole.LogToBC(userDetailsStr); + if (!string.IsNullOrWhiteSpace(userDetailsStr)) + { + var userBasicDetail = JsonConvert.DeserializeObject(userDetailsStr); + + var handler = new JwtSecurityTokenHandler(); + var jsontoken = handler.ReadToken(userBasicDetail.AccessToken) as JwtSecurityToken; + Setting.UserBasicDetails = userBasicDetail; + + if (jsontoken.ValidTo < DateTime.UtcNow) + { + bool isTokenRefreshed = await UserDataService.RefreshToken(); + + if (isTokenRefreshed) + { + NavManager.NavigateTo("/home"); + } + else + { + NavManager.NavigateTo("/login"); + } + + } + else + { + NavManager.NavigateTo("/home"); + } + } + else + { + NavManager.NavigateTo("/login"); + } + + } +} diff --git a/TIAMSharedUI/Pages/Components/Step2.razor b/TIAMSharedUI/Pages/Components/Step2.razor index 36381ffd..61f75004 100644 --- a/TIAMSharedUI/Pages/Components/Step2.razor +++ b/TIAMSharedUI/Pages/Components/Step2.razor @@ -15,7 +15,7 @@ Mask="\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*(\d{1,2})" MaskMode="@MaskMode.RegEx"> + PlaceholdersVisible=true/> diff --git a/TIAMSharedUI/Pages/Index.razor b/TIAMSharedUI/Pages/Index.razor index b5560892..8201f41f 100644 --- a/TIAMSharedUI/Pages/Index.razor +++ b/TIAMSharedUI/Pages/Index.razor @@ -1,4 +1,4 @@ -@page "/" +@page "/index" @using TIAMSharedUI.Shared Index diff --git a/TIAMSharedUI/Pages/Login.razor b/TIAMSharedUI/Pages/Login.razor index 379ff910..120cdf82 100644 --- a/TIAMSharedUI/Pages/Login.razor +++ b/TIAMSharedUI/Pages/Login.razor @@ -1,4 +1,4 @@ -@page "/login" +@page "/login_old" @using TIAMWebApp.Shared.Application.Interfaces; @using TIAMWebApp.Shared.Application.Models; @using TIAMWebApp.Shared.Application.Models.PageModels; diff --git a/TIAMSharedUI/Pages/Login2.razor b/TIAMSharedUI/Pages/Login2.razor index 04aff409..a1ee0510 100644 --- a/TIAMSharedUI/Pages/Login2.razor +++ b/TIAMSharedUI/Pages/Login2.razor @@ -1,10 +1,20 @@ -@page "/login2" +@page "/login" +@using System.IdentityModel.Tokens.Jwt; +@using System.Security.Claims; +@using Newtonsoft.Json.Linq; +@using System.Text.Json; +@using System.Reflection; @using TIAMWebApp.Shared.Application.Interfaces; @using TIAMWebApp.Shared.Application.Models.PageModels; @using TIAMSharedUI.Pages.Components; +@using TIAMWebApp.Shared.Application.Models.ClientSide; +@using TIAMWebApp.Shared.Application.Models; +@using TIAMWebApp.Shared.Application.Utility; @inject NavigationManager navManager +@inject LogToBrowserConsole logToBrowserConsole @inject IUserDataService UserDataservice @inject IJSRuntime jsRuntime +@inject ISecureStorageHandler SecureStorageHandler Login @@ -54,11 +64,11 @@ @code { LoginModel loginModel = new(); - + private int currentStep = 1; bool loggedIn = false; - + private void GoToNextStep() { @@ -73,58 +83,80 @@ private async void SubmitLogin() { - // Implement your registration logic here - // You can use Email, PhoneNumber, and Password variables - // Reset currentStep after successful registration - loggedIn = true; + currentStep = 1; - LogToBrowserConsole("Login started: " + "Email: " + loginModel.Email + ", Password: " + loginModel.Password); + logToBrowserConsole.LogToBC("Login started: " + "Email: " + loginModel.Email + ", Password: " + loginModel.Password); var response = await UserDataservice.AuthenticateUser(loginModel); //var response = await UserDataservice.TestUserApi(30); - LogToBrowserConsole("Login started"); - + logToBrowserConsole.LogToBC("Login started"); + logToBrowserConsole.LogToBC(response); if (!string.IsNullOrEmpty(response)) { - LogToBrowserConsole(response); - if (response == "no") + //get token and save to local storage + //parse to Mainresponse from json string + + + //var Mainresponse = JsonSerializer.Deserialize(response); + var Mainresponse = JsonSerializer.Deserialize(response, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (Mainresponse != null) { - //await App.Current.MainPage.DisplayAlert("Error", "Invalid credentials", "Ok"); - //display error message via jsinterop - LogToBrowserConsole("Invalid credentials"); - navManager.NavigateTo("login2"); + + //check for bad request + + string AuthResponseJson = JsonSerializer.Serialize(Mainresponse.Content); + + var AuthResponse = JsonSerializer.Deserialize(AuthResponseJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + string accessToken = AuthResponse.AccessToken; + + var handler = new JwtSecurityTokenHandler(); + var token = handler.ReadJwtToken(accessToken) as JwtSecurityToken; + + string _userId = token.Claims.First(claim => claim.Type == JwtRegisteredClaimNames.NameId).Value; + string _email = token.Claims.First(claim => claim.Type == JwtRegisteredClaimNames.Email).Value; + + var userBasicDetails = new UserBasicDetails(_userId, _email, AuthResponse.AccessToken, AuthResponse.RefreshToken); + + string userBasicDetailsJson = JsonSerializer.Serialize(userBasicDetails); + + + //save to local storage + await SecureStorageHandler.SaveToSecureStorageAsync(nameof(Setting.UserBasicDetails), userBasicDetailsJson); + + + if (!Mainresponse.IsSuccess) + { + //await App.Current.MainPage.DisplayAlert("Error", "Invalid credentials", "Ok"); + //display error message via jsinterop + logToBrowserConsole.LogToBC("Invalid credentials"); + navManager.NavigateTo("login2"); + } + else + { + //await App.Current.MainPage.DisplayAlert("Success", "Successful login", "Ok"); + //display success message via jsinterop + logToBrowserConsole.LogToBC("Successful login"); + var user = await UserDataservice.IsLoggedInAsync(); + user.IsLoggedIn = true; + + user.UserType = UserType.Admin; + navManager.NavigateTo("home"); + } + } - else if(response == "yes") - { - //await App.Current.MainPage.DisplayAlert("Success", "Successful login", "Ok"); - //display success message via jsinterop - LogToBrowserConsole("Successful login"); - navManager.NavigateTo("home"); - } - else - { - //await App.Current.MainPage.DisplayAlert("Error", "An error occured while trying to login", "Ok"); - //display error message via jsinterop - LogToBrowserConsole("An error occured while trying to login"); - navManager.NavigateTo("login2"); - } - + } else { //api error //await App.Current.MainPage.DisplayAlert("Error", "An error occured while trying to login", "Ok"); //display error message via jsinterop - LogToBrowserConsole("An error occured while trying to login"); + logToBrowserConsole.LogToBC("An error occured while trying to login"); navManager.NavigateTo("login2"); } - + } - public void LogToBrowserConsole(string message) - { - jsRuntime.InvokeVoidAsync("console.log", message); - } - - - + } \ No newline at end of file diff --git a/TIAMSharedUI/Pages/Register2.razor b/TIAMSharedUI/Pages/Register2.razor deleted file mode 100644 index 0e4b3455..00000000 --- a/TIAMSharedUI/Pages/Register2.razor +++ /dev/null @@ -1,35 +0,0 @@ -@page "/register2" -@inject NavigationManager navManager; -Register step 2 - -
- -
- Let's create your account! -
-
-
- - -
- - Previous - Next -
-
- Already have an account? Sign in here! -
-
- -@code { - private void next() - { - navManager.NavigateTo("register3"); - } - private void back() - { - navManager.NavigateTo("register"); - } -} diff --git a/TIAMSharedUI/Pages/Register2.razor.css b/TIAMSharedUI/Pages/Register2.razor.css deleted file mode 100644 index 8f676d9d..00000000 --- a/TIAMSharedUI/Pages/Register2.razor.css +++ /dev/null @@ -1,3 +0,0 @@ -.wrapper { - max-width: 400px; -} diff --git a/TIAMSharedUI/Pages/Register3.razor b/TIAMSharedUI/Pages/Register3.razor deleted file mode 100644 index 0680d36f..00000000 --- a/TIAMSharedUI/Pages/Register3.razor +++ /dev/null @@ -1,35 +0,0 @@ -@page "/register3" -@inject NavigationManager navManager; -Register step 3 - -
- -
- Let's create your account! -
-
-
- - -
- - Previous - Next -
-
- Already have an account? Sign in here! -
-
- -@code { - private void next() - { - navManager.NavigateTo("register4"); - } - private void back() - { - navManager.NavigateTo("register2"); - } -} diff --git a/TIAMSharedUI/Pages/Register3.razor.css b/TIAMSharedUI/Pages/Register3.razor.css deleted file mode 100644 index 8f676d9d..00000000 --- a/TIAMSharedUI/Pages/Register3.razor.css +++ /dev/null @@ -1,3 +0,0 @@ -.wrapper { - max-width: 400px; -} diff --git a/TIAMSharedUI/Pages/Register4.razor b/TIAMSharedUI/Pages/Register4.razor deleted file mode 100644 index b9a6501a..00000000 --- a/TIAMSharedUI/Pages/Register4.razor +++ /dev/null @@ -1,50 +0,0 @@ -@page "/register4" -@inject NavigationManager navManager; -Register step 4 - -
- -
- Let's create your account! -
-
-
- - -
-
- - -
-
- - -
- - - Previous - Next -
-
- Already have an account? Sign in here! -
-
- -@code { - private void next() - { - navManager.NavigateTo("login"); - } - private void back() - { - navManager.NavigateTo("register3"); - } -} diff --git a/TIAMSharedUI/Pages/Register4.razor.css b/TIAMSharedUI/Pages/Register4.razor.css deleted file mode 100644 index d4ee7806..00000000 --- a/TIAMSharedUI/Pages/Register4.razor.css +++ /dev/null @@ -1,65 +0,0 @@ -.wrapper { - max-width: 400px; -} - -label { - width: 100%; - font-size: 1rem; -} - -.card-input-element + .card { - height: calc(36px + 2*1rem); - color: var(--primary); - -webkit-box-shadow: none; - box-shadow: none; - border: 2px solid transparent; - border-radius: 4px; -} - - .card-input-element + .card:hover { - cursor: pointer; - } - -.card-input-element:checked + .card { - border: 2px solid var(--primary); - -webkit-transition: border .3s; - -o-transition: border .3s; - transition: border .3s; -} - - .card-input-element:checked + .card::after { - content: '\e5ca'; - color: #AFB8EA; - font-family: 'Material Icons'; - font-size: 24px; - -webkit-animation-name: fadeInCheckbox; - animation-name: fadeInCheckbox; - -webkit-animation-duration: .5s; - animation-duration: .5s; - -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - -@-webkit-keyframes fadeInCheckbox { - from { - opacity: 0; - -webkit-transform: rotateZ(-20deg); - } - - to { - opacity: 1; - -webkit-transform: rotateZ(0deg); - } -} - -@keyframes fadeInCheckbox { - from { - opacity: 0; - transform: rotateZ(-20deg); - } - - to { - opacity: 1; - transform: rotateZ(0deg); - } -} diff --git a/TIAMSharedUI/Shared/AdminLayout.razor b/TIAMSharedUI/Shared/AdminLayout.razor index 019a3243..e549aea1 100644 --- a/TIAMSharedUI/Shared/AdminLayout.razor +++ b/TIAMSharedUI/Shared/AdminLayout.razor @@ -14,12 +14,9 @@
- @{ - if (isUserLoggedIn) - { - - } - } + + + @Body
@@ -27,7 +24,7 @@ @code { - bool isUserLoggedIn; + /*bool isUserLoggedIn; int userType = 0; protected override async Task OnInitializedAsync() @@ -39,5 +36,5 @@ { NavigationManager.NavigateTo("/login"); } - } + }*/ } diff --git a/TIAMSharedUI/Shared/MainLayout.razor b/TIAMSharedUI/Shared/MainLayout.razor index 4598e1e2..d84847e3 100644 --- a/TIAMSharedUI/Shared/MainLayout.razor +++ b/TIAMSharedUI/Shared/MainLayout.razor @@ -1,12 +1,17 @@ @inherits LayoutComponentBase @using TIAMWebApp.Shared.Application.Interfaces +@using TIAMWebApp.Shared.Application.Models.ClientSide; @inject IUserDataService UserDataService; @inject IJSRuntime jsRuntime
-
- -
+ @if (Setting.UserBasicDetails != null) + { +
+ +
+ } +
@@ -43,16 +48,13 @@ protected override void OnAfterRender(bool isFirst) { - LogToBrowserConsole("0 "); + } - public void LogToBrowserConsole(string message) - { - jsRuntime.InvokeVoidAsync("console.log", message); - } + } diff --git a/TIAMSharedUI/Shared/NavMenu.razor b/TIAMSharedUI/Shared/NavMenu.razor index a6152645..b9d88571 100644 --- a/TIAMSharedUI/Shared/NavMenu.razor +++ b/TIAMSharedUI/Shared/NavMenu.razor @@ -29,12 +29,9 @@ Counter -
- - - Fetch data - + + +
+ diff --git a/TIAMWebApp/Client/Program.cs b/TIAMWebApp/Client/Program.cs index 91fa479c..52a0ff30 100644 --- a/TIAMWebApp/Client/Program.cs +++ b/TIAMWebApp/Client/Program.cs @@ -1,9 +1,12 @@ +using Blazored.LocalStorage; using DevExpress.Blazor; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.JSInterop; using TIAMWebApp.Client; using TIAMWebApp.Client.Services; using TIAMWebApp.Shared.Application.Interfaces; +using TIAMWebApp.Shared.Application.Utility; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -13,7 +16,15 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddBlazoredLocalStorage(); +//WebSpecific +builder.Services.AddScoped(); +//WebSpecific end + + builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddDevExpressBlazor(configure => configure.BootstrapVersion = BootstrapVersion.v5); await builder.Build().RunAsync(); diff --git a/TIAMWebApp/Client/Services/LocalStorageAccessor.cs b/TIAMWebApp/Client/Services/LocalStorageAccessor.cs new file mode 100644 index 00000000..69672d8f --- /dev/null +++ b/TIAMWebApp/Client/Services/LocalStorageAccessor.cs @@ -0,0 +1,31 @@ +using Microsoft.JSInterop; + +namespace TIAMWebApp.Client.Services +{ + public class LocalStorageAccessor : IAsyncDisposable + { + private Lazy _accessorJsRef = new(); + private readonly IJSRuntime _jsRuntime; + + public LocalStorageAccessor(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + + private async Task WaitForReference() + { + if (_accessorJsRef.IsValueCreated is false) + { + _accessorJsRef = new(await _jsRuntime.InvokeAsync("import", "/js/LocalStorageAccessor.js")); + } + } + + public async ValueTask DisposeAsync() + { + if (_accessorJsRef.IsValueCreated) + { + await _accessorJsRef.Value.DisposeAsync(); + } + } + } +} diff --git a/TIAMWebApp/Client/Services/SecureStorageHandler.cs b/TIAMWebApp/Client/Services/SecureStorageHandler.cs new file mode 100644 index 00000000..9bfd97d2 --- /dev/null +++ b/TIAMWebApp/Client/Services/SecureStorageHandler.cs @@ -0,0 +1,33 @@ +using Blazored.LocalStorage; +using TIAMWebApp.Shared.Application.Interfaces; + +namespace TIAMWebApp.Client.Services +{ + public class SecureStorageHandler : ISecureStorageHandler + { + private readonly SessionStorageAccessor ssa; + private readonly ILocalStorageService localStoragService; + + public SecureStorageHandler(SessionStorageAccessor ssa, ILocalStorageService localStorageService) + { + this.ssa = ssa; + this.localStoragService = localStorageService; + } + + + public async Task SaveToSecureStorageAsync(string key, string value) + { + await localStoragService.SetItemAsync(key, value); + await ssa.SetValueAsync(key, value); + + + } + + public async Task GetFromSecureStorageAsync(string key) + { + return await localStoragService.GetItemAsync(key); + return await ssa.GetValueAsync(key); + + } + } +} diff --git a/TIAMWebApp/Client/Services/SessionStorageAccessor.cs b/TIAMWebApp/Client/Services/SessionStorageAccessor.cs new file mode 100644 index 00000000..6104c262 --- /dev/null +++ b/TIAMWebApp/Client/Services/SessionStorageAccessor.cs @@ -0,0 +1,58 @@ +using Microsoft.JSInterop; + +namespace TIAMWebApp.Client.Services +{ + public class SessionStorageAccessor : IAsyncDisposable + { + private Lazy _accessorJsRef = new(); + private readonly IJSRuntime _jsRuntime; + + public SessionStorageAccessor(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + + private async Task WaitForReference() + { + if (_accessorJsRef.IsValueCreated is false) + { + _accessorJsRef = new(await _jsRuntime.InvokeAsync("import", "/js/SessionStorageAccessor.js")); + } + } + + public async ValueTask DisposeAsync() + { + if (_accessorJsRef.IsValueCreated) + { + await _accessorJsRef.Value.DisposeAsync(); + } + } + + public async Task GetValueAsync(string key) + { + await WaitForReference(); + var result = await _accessorJsRef.Value.InvokeAsync("get", key); + + return result; + } + + public async Task SetValueAsync(string key, T value) + { + await WaitForReference(); + await _accessorJsRef.Value.InvokeVoidAsync("set", key, value); + } + + public async Task Clear() + { + await WaitForReference(); + await _accessorJsRef.Value.InvokeVoidAsync("clear"); + } + + public async Task RemoveAsync(string key) + { + await WaitForReference(); + await _accessorJsRef.Value.InvokeVoidAsync("remove", key); + } + + } +} diff --git a/TIAMWebApp/Client/Services/UserDataService.cs b/TIAMWebApp/Client/Services/UserDataService.cs index 1a4e8139..8ee2ee97 100644 --- a/TIAMWebApp/Client/Services/UserDataService.cs +++ b/TIAMWebApp/Client/Services/UserDataService.cs @@ -4,6 +4,7 @@ using System.Net.Http.Json; using System.Text; using TIAMWebApp.Shared.Application.Interfaces; using TIAMWebApp.Shared.Application.Models; +using TIAMWebApp.Shared.Application.Models.ClientSide; using TIAMWebApp.Shared.Application.Models.PageModels; @@ -12,12 +13,14 @@ namespace TIAMWebApp.Client.Services public class UserDataService : IUserDataService { private readonly HttpClient http; - public User? User { get; set; } = new User("","",""); + private readonly ISecureStorageHandler secureStorageHandler; + public User? User { get; set; } = new User("", "", ""); public Dictionary userRoleTypes { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public UserDataService(HttpClient http) + public UserDataService(HttpClient http, ISecureStorageHandler secureStorageHandler) { this.http = http; + this.secureStorageHandler = secureStorageHandler; } @@ -40,7 +43,7 @@ namespace TIAMWebApp.Client.Services { if (User == null) { - User = new User("","",""); + User = new User("", "", ""); User.IsLoggedIn = false; User.UserType = UserType.User; return User; @@ -50,7 +53,7 @@ namespace TIAMWebApp.Client.Services { return User; } - + } //Mock method for now @@ -66,7 +69,8 @@ namespace TIAMWebApp.Client.Services return User; } - public async Task TestUserApi(int Param) { + public async Task TestUserApi(int Param) + { var url = APIUrls.UserTest; var response = await http.PostAsJsonAsync(url, Param); var result = await response.Content.ReadAsStringAsync(); @@ -75,15 +79,15 @@ namespace TIAMWebApp.Client.Services public async Task AuthenticateUser(LoginModel loginModel) { - - - string result = string.Empty; - var url = APIUrls.AuthenticateUser; - + + + string result = string.Empty; + var url = APIUrls.AuthenticateUser; + var response = await http.PostAsJsonAsync(url, loginModel); - if(response.IsSuccessStatusCode) + if (response.IsSuccessStatusCode) { result = await response.Content.ReadAsStringAsync(); } @@ -95,7 +99,7 @@ namespace TIAMWebApp.Client.Services //result = await response.Content.ReadAsStringAsync(); return result; - + } public async Task<(bool isSuccess, string ErrorMessage)> CreateUser(RegistrationModel regModel) @@ -105,7 +109,7 @@ namespace TIAMWebApp.Client.Services string result = string.Empty; var url = APIUrls.CreateUser; - var response = await http.PostAsJsonAsync(url, regModel); + var response = await http.PostAsJsonAsync(url, regModel); result = await response.Content.ReadAsStringAsync(); /*if (response.IsSuccessStatusCode) { @@ -122,25 +126,67 @@ namespace TIAMWebApp.Client.Services return (isSuccess, result); } + public async Task RefreshToken() + { + bool isTokenRefreshed = false; + using (var client = new HttpClient()) + { + var url = APIUrls.RefreshToken; + + var serializedStr = JsonConvert.SerializeObject(new AuthenticateRequestAndResponse + { + RefreshToken = Setting.UserBasicDetails.RefreshToken, + AccessToken = Setting.UserBasicDetails.AccessToken + }); + + try + { + var response = await client.PostAsync(url, new StringContent(serializedStr, Encoding.UTF8, "application/json")); + if (response.IsSuccessStatusCode) + { + string contentStr = await response.Content.ReadAsStringAsync(); + var mainResponse = JsonConvert.DeserializeObject(contentStr); + if (mainResponse.IsSuccess) + { + var tokenDetails = JsonConvert.DeserializeObject(mainResponse.Content.ToString()); + Setting.UserBasicDetails.AccessToken = tokenDetails.AccessToken; + Setting.UserBasicDetails.RefreshToken = tokenDetails.RefreshToken; + + string userDetailsStr = JsonConvert.SerializeObject(Setting.UserBasicDetails); + await secureStorageHandler.SaveToSecureStorageAsync(nameof(Setting.UserBasicDetails), userDetailsStr); + isTokenRefreshed = true; + } + } + } + catch (Exception ex) + { + string msg = ex.Message; + } + + + } + return isTokenRefreshed; + } + public Task> GetUserRolesAsync(User user) { //get the user's roles - int role = User.UserRoles; + int role = User.UserRoles; - foreach (var roleType in roleTypes) + foreach (var roleType in roleTypes) + { + if ((role & roleType.Id) == roleType.Id) { - if ((role & roleType.Id) == roleType.Id) - { - - //add the role to the dictionary - userRoleTypes.Add(roleType.Id, roleType.RoleName); - } + //add the role to the dictionary + userRoleTypes.Add(roleType.Id, roleType.RoleName); + } + } return Task.FromResult(userRoleTypes); - + } } } diff --git a/TIAMWebApp/Client/TIAMWebApp.Client.csproj b/TIAMWebApp/Client/TIAMWebApp.Client.csproj index 7e92e964..80219aeb 100644 --- a/TIAMWebApp/Client/TIAMWebApp.Client.csproj +++ b/TIAMWebApp/Client/TIAMWebApp.Client.csproj @@ -7,6 +7,7 @@ + diff --git a/TIAMWebApp/Client/wwwroot/js/LocalStorageAccessor.js b/TIAMWebApp/Client/wwwroot/js/LocalStorageAccessor.js new file mode 100644 index 00000000..d9335ba7 --- /dev/null +++ b/TIAMWebApp/Client/wwwroot/js/LocalStorageAccessor.js @@ -0,0 +1,15 @@ +export function get(key) { + return window.sessionStorage.getItem(key); +} + +export function set(key, value) { + window.sessionStorage.setItem(key, value); +} + +export function clear() { + window.sessionStorage.clear(); +} + +export function remove(key) { + window.sessionStorage.removeItem(key); +} \ No newline at end of file diff --git a/TIAMWebApp/Client/wwwroot/js/SessionStorageAccessor.js b/TIAMWebApp/Client/wwwroot/js/SessionStorageAccessor.js new file mode 100644 index 00000000..d9335ba7 --- /dev/null +++ b/TIAMWebApp/Client/wwwroot/js/SessionStorageAccessor.js @@ -0,0 +1,15 @@ +export function get(key) { + return window.sessionStorage.getItem(key); +} + +export function set(key, value) { + window.sessionStorage.setItem(key, value); +} + +export function clear() { + window.sessionStorage.clear(); +} + +export function remove(key) { + window.sessionStorage.removeItem(key); +} \ No newline at end of file diff --git a/TIAMWebApp/Server/Controllers/UserAPIController.cs b/TIAMWebApp/Server/Controllers/UserAPIController.cs index 89c8d6c4..4e5fad33 100644 --- a/TIAMWebApp/Server/Controllers/UserAPIController.cs +++ b/TIAMWebApp/Server/Controllers/UserAPIController.cs @@ -5,35 +5,48 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; -using System.Reflection.Metadata; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; using System.Text.Json; using TIAMWebApp.Shared.Application.Models; using TIAMWebApp.Shared.Application.Models.PageModels; +using TIAMWebApp.Server.Models; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using TIAMWebApp.Server.ModelsTIAMWebApp.Shared.Application.Models; namespace TIAMWebApp.Server.Controllers { + [Authorize] [ApiController] - [Route("[controller]")] + [Route("api/[controller]")] public class UserAPIController : ControllerBase { + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _webHostEnvironment; PasswordHasher hasher = new PasswordHasher(); private User[] users = new User[] { - new User("test@tiam.hu", "+36701234567", "asd123") + new User(new Guid("540271f6-c604-4c16-8160-d5a7cafedf00"), "test@tiam.hu", "+36701234567", "Asdasd123456"), + new User(new Guid("4cbaed43-2465-4d99-84f1-c8bc6b7025f7"), "adam@tiam.hu", "+36701234567", "Asdasd987654") }; - private readonly ILogger _logger; + private readonly ILogger _logger; - public UserAPIController(ILogger logger) + public UserAPIController(ILogger logger, IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { _logger = logger; + _configuration = configuration; + _webHostEnvironment = webHostEnvironment; } - [HttpPost] + /*[HttpPost] [Route("Auth")] public async Task AuthenticateUser([FromBody] JsonElement SerializedLoginModel) { @@ -50,7 +63,7 @@ namespace TIAMWebApp.Server.Controllers Console.WriteLine(user.Email); Console.WriteLine(user.Password); - if (user.Email == "test@tiam.hu" && user.Password == "asd123") + if (user.Email == "test@tiam.hu" && user.Password == "Asdasd123456") { Console.WriteLine("User authenticated"); return Ok("yes"); @@ -62,8 +75,178 @@ namespace TIAMWebApp.Server.Controllers } } + }*/ + + [AllowAnonymous] + [HttpPost("AuthenticateUser")] + public async Task AuthenticateUser([FromBody] JsonElement SerializedLoginModel) + { + + var authenticateUser = JObject.Parse(SerializedLoginModel.GetRawText()).ToObject(); + + //check if user exists + //var user = await _userManager.FindByNameAsync(authenticateUser.UserName); + //if (user == null) return Unauthorized(); + + //mocking + var user = users.FirstOrDefault(x => x.Email == authenticateUser.Email); + + //check if password is valid + //bool isValidUser = await _userManager.CheckPasswordAsync(user, authenticateUser.Password); + + //mocking + bool isValidUser = false; + + if (user.Password == authenticateUser.Password) + { + isValidUser = true; + + } + + if (isValidUser) + { + Console.WriteLine("User authenticated, let's start JWT"); + string accessToken = GenerateAccessToken(user); + Console.WriteLine("Generate refresh token"); + var refreshToken = GenerateRefreshToken(); + user.RefreshToken = refreshToken; + //Update user with refreshToken!! + //await _userManager.UpdateAsync(user); + + var response = new MainResponse + { + Content = new AuthenticationResponse + { + RefreshToken = refreshToken, + AccessToken = accessToken + }, + IsSuccess = true, + ErrorMessage = "" + }; + return Ok(response); + } + else + { + return Unauthorized(); + } + } + private string GenerateAccessToken(User user) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var token = new JwtSecurityToken(); + Console.WriteLine("----------------------------------------------------------"); + var keyDetail = Encoding.UTF8.GetBytes(_configuration["JWT:Key"]); + Console.WriteLine(_configuration["JWT:Key"]); + + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Email, user.Email) + }; + + var tokenDescriptor = new SecurityTokenDescriptor + { + Audience = _configuration["JWT:Audience"], + Issuer = _configuration["JWT:Issuer"], + Expires = DateTime.UtcNow.AddMinutes(30), + Subject = new ClaimsIdentity(claims), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyDetail), SecurityAlgorithms.HmacSha256Signature) + }; + token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken; + string writtenToken = tokenHandler.WriteToken(token); + Console.WriteLine(writtenToken); + return writtenToken; + } + + [AllowAnonymous] + [HttpPost("RefreshToken")] + public async Task RefreshToken(RefreshTokenRequest refreshTokenRequest) + { + var response = new MainResponse(); + if (refreshTokenRequest is null) + { + response.ErrorMessage = "Invalid request"; + return BadRequest(response); + } + + var principal = GetPrincipalFromExpiredToken(refreshTokenRequest.AccessToken); + + if (principal != null) + { + var email = principal.Claims.FirstOrDefault(f => f.Type == ClaimTypes.Email); + + //var user = await _userManager.FindByEmailAsync(email?.Value); + var user = users.FirstOrDefault(x => x.Email == email?.Value); + + + if (user is null || user.RefreshToken != refreshTokenRequest.RefreshToken) + { + response.ErrorMessage = "Invalid Request"; + return BadRequest(response); + } + + string newAccessToken = GenerateAccessToken(user); + string refreshToken = GenerateRefreshToken(); + + //mocking - update user with new refreshToken + user.RefreshToken = refreshToken; + //await _userManager.UpdateAsync(user); + + response.IsSuccess = true; + response.Content = new AuthenticationResponse + { + RefreshToken = refreshToken, + AccessToken = newAccessToken + }; + return Ok(response); + } + else + { + return NotFound("Invalid Token Found"); + } + + } + + private ClaimsPrincipal GetPrincipalFromExpiredToken(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + + var keyDetail = Encoding.UTF8.GetBytes(_configuration["JWT:Key"]); + + var tokenValidationParameter = new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + ValidateIssuerSigningKey = true, + ValidIssuer = _configuration["JWT:Issuer"], + ValidAudience = _configuration["JWT:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(keyDetail), + }; + + SecurityToken securityToken; + var principal = tokenHandler.ValidateToken(token, tokenValidationParameter, out securityToken); + var jwtSecurityToken = securityToken as JwtSecurityToken; + if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) + throw new SecurityTokenException("Invalid token"); + return principal; + } + + private string GenerateRefreshToken() + { + + var randomNumber = new byte[32]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + } + + + [HttpPost] [Route("CreateUser")] public async Task CreateUser([FromBody] JsonElement SerializedRegistrationModel) diff --git a/TIAMWebApp/Server/Models/AuthenticateUser.cs b/TIAMWebApp/Server/Models/AuthenticateUser.cs new file mode 100644 index 00000000..61916b95 --- /dev/null +++ b/TIAMWebApp/Server/Models/AuthenticateUser.cs @@ -0,0 +1,10 @@ +namespace TIAMWebApp.Server.Models +{ + + public class AuthenticateUser + { + public string Email { get; set; } + public string Password { get; set; } + } + +} diff --git a/TIAMWebApp/Server/Models/RefreshTokenRequest.cs b/TIAMWebApp/Server/Models/RefreshTokenRequest.cs new file mode 100644 index 00000000..5d811627 --- /dev/null +++ b/TIAMWebApp/Server/Models/RefreshTokenRequest.cs @@ -0,0 +1,8 @@ +namespace TIAMWebApp.Server.Models +{ + public class RefreshTokenRequest + { + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } +} diff --git a/TIAMWebApp/Server/Program.cs b/TIAMWebApp/Server/Program.cs index c833b303..3bf7664c 100644 --- a/TIAMWebApp/Server/Program.cs +++ b/TIAMWebApp/Server/Program.cs @@ -6,6 +6,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TIAM.Entities.TransferDestinations; using TIAM.Database.DbContexts; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Text; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using TIAMWebApp.Shared.Application.Models; var builder = WebApplication.CreateBuilder(args); @@ -15,6 +20,71 @@ builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DeveloperDbConnection")));; +builder.Services.AddSwaggerGen(swagger => +{ + swagger.SwaggerDoc("v1", + new OpenApiInfo + { + Title = "API Title", + Version = "V1", + Description = "API Description" + }); + + var securitySchema = new OpenApiSecurityScheme + { + Description = "Authorization header using the Bearer scheme. Example \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "Bearer", + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }; + swagger.AddSecurityDefinition(securitySchema.Reference.Id, securitySchema); + swagger.AddSecurityRequirement(new OpenApiSecurityRequirement + { + {securitySchema,Array.Empty() } + }); +}); + +builder.Services.AddAuthentication(f => +{ + f.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + f.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(k => +{ + var Key = Encoding.UTF8.GetBytes(builder.Configuration["JWT:Key"]); + k.SaveToken = true; + k.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = builder.Configuration["JWT:Issuer"], + ValidAudience = builder.Configuration["JWT:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Key), + ClockSkew = TimeSpan.Zero + }; + +}); + + + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.ConfigureApplicationCookie(options => +{ + options.Cookie.HttpOnly = false; + options.ExpireTimeSpan = TimeSpan.FromMinutes(5); + options.LoginPath = "/Login"; + options.SlidingExpiration = true; +}); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -29,12 +99,21 @@ else app.UseHsts(); } +app.UseSwagger(); +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); +}); + + //app.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); +app.UseAuthentication(); app.UseRouting(); +app.UseAuthorization(); app.MapRazorPages(); diff --git a/TIAMWebApp/Server/TIAMWebApp.Server.csproj b/TIAMWebApp/Server/TIAMWebApp.Server.csproj index 6e837648..da73b9ed 100644 --- a/TIAMWebApp/Server/TIAMWebApp.Server.csproj +++ b/TIAMWebApp/Server/TIAMWebApp.Server.csproj @@ -8,8 +8,13 @@ + + + + + diff --git a/TIAMWebApp/Server/appsettings.Development.json b/TIAMWebApp/Server/appsettings.Development.json index 0c208ae9..78a8d7c0 100644 --- a/TIAMWebApp/Server/appsettings.Development.json +++ b/TIAMWebApp/Server/appsettings.Development.json @@ -4,5 +4,10 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "JWT": { + "Key": "Cee4400-rDMFkVvHPufyLDSzbfu2grgRhpepos299IhTLOXsljkcpt3yUR4RRjPQ", + "Issuer": "https://localhost:7116", + "Audience": "http://localhost:7116" + } } diff --git a/TIAMWebApp/Server/appsettings.json b/TIAMWebApp/Server/appsettings.json index a6d64f47..8667b4b9 100644 --- a/TIAMWebApp/Server/appsettings.json +++ b/TIAMWebApp/Server/appsettings.json @@ -8,5 +8,10 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "JWT": { + "Key": "Cee4400-rDMFkVvHPufyLDSzbfu2grgRhpepos299IhTLOXsljkcpt3yUR4RRjPQ", + "Issuer": "http://localhost:5000", + "Audience": "http://localhost:5000" + } } diff --git a/TIAMWebApp/Shared/Interfaces/ISecureStorageHandler.cs b/TIAMWebApp/Shared/Interfaces/ISecureStorageHandler.cs new file mode 100644 index 00000000..9f213316 --- /dev/null +++ b/TIAMWebApp/Shared/Interfaces/ISecureStorageHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TIAMWebApp.Shared.Application.Interfaces +{ + public interface ISecureStorageHandler + { + public Task SaveToSecureStorageAsync(string key, string value); + public Task GetFromSecureStorageAsync(string key); + } +} diff --git a/TIAMWebApp/Shared/Interfaces/IUserDataService.cs b/TIAMWebApp/Shared/Interfaces/IUserDataService.cs index 5ae643c3..81cbad46 100644 --- a/TIAMWebApp/Shared/Interfaces/IUserDataService.cs +++ b/TIAMWebApp/Shared/Interfaces/IUserDataService.cs @@ -23,5 +23,7 @@ namespace TIAMWebApp.Shared.Application.Interfaces public Task TestUserApi(int Param); public Task> GetUserRolesAsync(User user); + + Task RefreshToken(); } } \ No newline at end of file diff --git a/TIAMWebApp/Shared/Models/APIUrls.cs b/TIAMWebApp/Shared/Models/APIUrls.cs index ab3bb3b2..ec39ddf3 100644 --- a/TIAMWebApp/Shared/Models/APIUrls.cs +++ b/TIAMWebApp/Shared/Models/APIUrls.cs @@ -8,9 +8,10 @@ namespace TIAMWebApp.Shared.Application.Models { public class APIUrls { - public const string UserTest = "UserAPI/test1"; - public const string AuthenticateUser = "UserAPI/Auth"; - public const string CreateUser = "UserAPI/CreateUser"; + public const string UserTest = "api/UserAPI/test1"; + public const string AuthenticateUser = "api/UserAPI/AuthenticateUser"; + public const string CreateUser = "api/UserAPI/CreateUser"; + public const string RefreshToken = "api/UserAPI/RefreshToken"; public const string WeatherForecast = "WeatherForecastAPI"; public const string PopulationStructure = "PopulationStructureAPI"; } diff --git a/TIAMWebApp/Shared/Models/AuthenticateRequestAndResponse.cs b/TIAMWebApp/Shared/Models/AuthenticateRequestAndResponse.cs new file mode 100644 index 00000000..bb5d5bf6 --- /dev/null +++ b/TIAMWebApp/Shared/Models/AuthenticateRequestAndResponse.cs @@ -0,0 +1,9 @@ + +namespace TIAMWebApp.Shared.Application.Models +{ + public class AuthenticateRequestAndResponse + { + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } +} diff --git a/TIAMWebApp/Shared/Models/AuthenticationResponse.cs b/TIAMWebApp/Shared/Models/AuthenticationResponse.cs new file mode 100644 index 00000000..516f61e8 --- /dev/null +++ b/TIAMWebApp/Shared/Models/AuthenticationResponse.cs @@ -0,0 +1,9 @@ +namespace TIAMWebApp.Shared.Application.Models +{ + public class AuthenticationResponse + { + public string? AccessToken { get; set; } + public string? RefreshToken { get; set; } + + } +} diff --git a/TIAMWebApp/Shared/Models/ClientSide/Setting.cs b/TIAMWebApp/Shared/Models/ClientSide/Setting.cs new file mode 100644 index 00000000..0a74e82b --- /dev/null +++ b/TIAMWebApp/Shared/Models/ClientSide/Setting.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TIAMWebApp.Shared.Application.Models.ClientSide +{ + public class Setting + { + public static UserBasicDetails UserBasicDetails { get; set; } + } +} diff --git a/TIAMWebApp/Shared/Models/ClientSide/UserBasicDetails.cs b/TIAMWebApp/Shared/Models/ClientSide/UserBasicDetails.cs new file mode 100644 index 00000000..731bb662 --- /dev/null +++ b/TIAMWebApp/Shared/Models/ClientSide/UserBasicDetails.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TIAMWebApp.Shared.Application.Models.ClientSide +{ + public class UserBasicDetails + { + public string UserId { get; set; } + public string Email { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + + public UserBasicDetails(string userId, string email, string token, string refreshToken) + { + UserId = userId; + Email = email; + AccessToken = token; + RefreshToken = refreshToken; + } + } +} diff --git a/TIAMWebApp/Shared/Models/ErrorResponse.cs b/TIAMWebApp/Shared/Models/ErrorResponse.cs new file mode 100644 index 00000000..784f912a --- /dev/null +++ b/TIAMWebApp/Shared/Models/ErrorResponse.cs @@ -0,0 +1,16 @@ +using TIAMWebApp.Shared.Application.Models; + +namespace TIAMWebApp.Server.ModelsTIAMWebApp.Shared.Application.Models +{ + public class ErrorResponse + { + public static MainResponse ReturnErrorResponse(string errorMessage) + { + return new MainResponse + { + ErrorMessage = errorMessage, + IsSuccess = true + }; + } + } +} diff --git a/TIAMWebApp/Shared/Models/MainResponse.cs b/TIAMWebApp/Shared/Models/MainResponse.cs new file mode 100644 index 00000000..bfcd82e2 --- /dev/null +++ b/TIAMWebApp/Shared/Models/MainResponse.cs @@ -0,0 +1,9 @@ +namespace TIAMWebApp.Shared.Application.Models +{ + public class MainResponse + { + public bool IsSuccess { get; set; } + public string? ErrorMessage { get; set; } + public object? Content { get; set; } + } +} diff --git a/TIAMWebApp/Shared/Models/User.cs b/TIAMWebApp/Shared/Models/User.cs index 6d378bf8..99329e5d 100644 --- a/TIAMWebApp/Shared/Models/User.cs +++ b/TIAMWebApp/Shared/Models/User.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TIAMWebApp.Shared.Application.Models +namespace TIAMWebApp.Shared.Application.Models { - public class User + public class User { public Guid Id { get; set; } public string? Email { get; set; } @@ -15,6 +9,7 @@ namespace TIAMWebApp.Shared.Application.Models public bool IsLoggedIn { get; set; } public UserType UserType { get; set; } public int UserRoles { get; set; } + public string? RefreshToken { get; set; } public Dictionary UserRolesDictionary { get; set; } public User(string email, string phonenumber, string password) @@ -26,6 +21,15 @@ namespace TIAMWebApp.Shared.Application.Models UserRolesDictionary = new Dictionary(); } + public User(Guid id, string email, string phonenumber, string password) + { + Id = id; + Email = email; + Password = password; + PhoneNumber = phonenumber; + UserRolesDictionary = new Dictionary(); + } + } public enum UserType diff --git a/TIAMWebApp/Shared/TIAMWebApp.Shared.Application.csproj b/TIAMWebApp/Shared/TIAMWebApp.Shared.Application.csproj index 25a181bc..16aad7a3 100644 --- a/TIAMWebApp/Shared/TIAMWebApp.Shared.Application.csproj +++ b/TIAMWebApp/Shared/TIAMWebApp.Shared.Application.csproj @@ -16,6 +16,7 @@ + diff --git a/TIAMWebApp/Shared/Utility/LogToBrowserConsole.cs b/TIAMWebApp/Shared/Utility/LogToBrowserConsole.cs index 2e88f529..94781138 100644 --- a/TIAMWebApp/Shared/Utility/LogToBrowserConsole.cs +++ b/TIAMWebApp/Shared/Utility/LogToBrowserConsole.cs @@ -1,21 +1,10 @@ using Microsoft.JSInterop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TIAMWebApp.Shared.Application.Models; namespace TIAMWebApp.Shared.Application.Utility { - internal class LogToBrowserConsole + public class LogToBrowserConsole { - private readonly JSRuntime jsRuntime; - - public LogToBrowserConsole(JSRuntime jSRuntime) - { - this.jsRuntime = jsRuntime; - } + private readonly JSRuntime jsRuntime; public void LogToBC(string message) {