From 8b6af574901fdcb09a04cfca87b85a83e4395fd6 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Tue, 18 Nov 2025 20:00:49 +0300 Subject: [PATCH 01/15] feat: keeping jwt token in HttpOnly Secure Coockie as opposed to localStorage --- .../Controllers/AccountController.cs | 53 +++++++++++- .../HwProj.APIGateway.API/Startup.cs | 41 +++++++-- .../Controllers/AccountController.cs | 4 +- .../Models/IdentityContext.cs | 2 +- .../Services/AccountService.cs | 1 + .../Services/AuthTokenService.cs | 11 +-- .../AuthService/DTO/TokenCredentials.cs | 5 +- hwproj.front/package-lock.json | 83 ++++++++----------- hwproj.front/src/App.tsx | 2 +- 9 files changed, 134 insertions(+), 68 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs index cf6c21e1f..879b45833 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs @@ -12,6 +12,7 @@ using HwProj.Models.Roles; using HwProj.SolutionsService.Client; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace HwProj.APIGateway.API.Controllers @@ -102,7 +103,30 @@ public async Task Register(RegisterViewModel model) public async Task Login(LoginViewModel model) { var tokenMeta = await AuthServiceClient.Login(model).ConfigureAwait(false); - return Ok(tokenMeta); + if (!tokenMeta.Succeeded) + { + ClearTokenCookie(); + //return BadRequest(tokenMeta); + return Unauthorized(); + } + + Response.Cookies.Append("accessToken", tokenMeta.Value.AccessToken, + new CookieOptions + { + Expires = tokenMeta.Value.ExpiresIn, + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict + }); + + if (string.IsNullOrEmpty(tokenMeta.Value.RefreshToken)) + { + RefreshToken(); + } + + // var antiForgeryToken = ); + + return Ok( tokenMeta.Succeeded ); } [Authorize] @@ -111,7 +135,32 @@ public async Task Login(LoginViewModel model) public async Task RefreshToken() { var tokenMeta = await AuthServiceClient.RefreshToken(UserId!); - return Ok(tokenMeta); + Response.Cookies.Append("refreshToken", tokenMeta.Value.AccessToken, + new CookieOptions + { + Expires = tokenMeta.Value.ExpiresIn, + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict + }); + return Ok(tokenMeta.Succeeded); + } + + [HttpPost("logout")] + [AllowAnonymous] + public IActionResult Logout() + { + ClearTokenCookie(); + return Ok(); + } + + private void ClearTokenCookie() + { + if (Request.Cookies.ContainsKey("accessToken")) + { + Response.Cookies.Delete("accessToken"); + Response.Cookies.Delete("refreshToken"); + } } [HttpPut("edit")] diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index ec49a65cd..d7002eb2f 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; using System.Text.Json.Serialization; +using System.Threading.Tasks; using HwProj.APIGateway.API.Filters; using HwProj.AuthService.Client; using HwProj.ContentService.Client; @@ -10,6 +12,8 @@ using IStudentsInfo; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.CookiePolicy; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +31,7 @@ public Startup(IConfiguration configuration) Configuration = configuration; } - public IConfiguration Configuration { get; } + private IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { @@ -57,7 +61,7 @@ public void ConfigureServices(IServiceCollection services) }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x => { - x.RequireHttpsMetadata = false; + x.RequireHttpsMetadata = true; x.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = "AuthService", @@ -66,13 +70,35 @@ public void ConfigureServices(IServiceCollection services) ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings["SecurityKey"])), - ValidateIssuerSigningKey = true + ValidateIssuerSigningKey = true, + ClockSkew = TimeSpan.Zero + }; + x.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + context.Request.Cookies.TryGetValue("accessToken", out var accessToken); + + if (!string.IsNullOrEmpty(accessToken)) + { + context.Token = accessToken; + } + + return Task.CompletedTask; + }, + + //? + OnAuthenticationFailed = context => + { + if (context.Exception is SecurityTokenExpiredException){ + context.Response.Headers.Add("Token-Expired", "true"); + } + return Task.CompletedTask; + } }; }); services.AddHttpClient(); - services.AddHttpContextAccessor(); - services.AddAuthServiceClient(); services.AddCoursesServiceClient(); services.AddSolutionServiceClient(); @@ -97,9 +123,8 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env) app.UseCors(x => x .AllowAnyMethod() .AllowAnyHeader() - .SetIsOriginAllowed(_ => true) + .SetIsOriginAllowed(_ => true) // Configuration["Cors:AllowOrigins "]).Split(';'); .AllowCredentials()); - app.UseEndpoints(x => x.MapControllers()); } diff --git a/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs b/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs index 06e2bf26f..27b10e067 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using HwProj.Models.AuthService.ViewModels; using HwProj.Models.Result; using HwProj.Models.Roles; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using User = HwProj.AuthService.API.Models.User; diff --git a/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs b/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs index 564a283e1..e7b98f880 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs @@ -6,7 +6,7 @@ namespace HwProj.AuthService.API.Models public sealed class IdentityContext : IdentityDbContext { public DbSet ExpertsData { get; set; } - + public IdentityContext(DbContextOptions options) : base(options) { diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs index 956f3589e..9a7c700a7 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs @@ -13,6 +13,7 @@ using HwProj.Models.AuthService.ViewModels; using HwProj.Models.Result; using HwProj.NotificationService.Events.AuthService; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Octokit; diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/AuthTokenService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/AuthTokenService.cs index 528a019ab..33e0aee30 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/AuthTokenService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/AuthTokenService.cs @@ -40,7 +40,7 @@ public async Task GetTokenAsync(User user) var expiresIn = userRoles.FirstOrDefault() == Roles.ExpertRole ? GetExpertTokenExpiresIn(timeNow) : timeNow.AddMinutes(int.Parse(_configuration["ExpiresIn"])); - + var token = new JwtSecurityToken( issuer: _configuration["ApiName"], notBefore: timeNow, @@ -56,7 +56,8 @@ public async Task GetTokenAsync(User user) var tokenCredentials = new TokenCredentials { - AccessToken = _tokenHandler.WriteToken(token) + AccessToken = _tokenHandler.WriteToken(token), + ExpiresIn = expiresIn }; return tokenCredentials; @@ -80,16 +81,16 @@ public async Task> GetExpertTokenAsync(User expert) .Select(errors => errors.Description) .ToArray()); } - + var tokenCredentials = await GetTokenAsync(expert); var result = await _userManager.SetAuthenticationTokenAsync(expert, loginProvider, tokenName, tokenCredentials.AccessToken); if (result.Succeeded) return Result.Success(tokenCredentials); - + return Result.Failed(result.Errors.Select(errors => errors.Description) .ToArray()); } - + return Result.Success(new TokenCredentials { AccessToken = token diff --git a/HwProj.Common/HwProj.Models/AuthService/DTO/TokenCredentials.cs b/HwProj.Common/HwProj.Models/AuthService/DTO/TokenCredentials.cs index 8d0d8ad7f..24629578a 100644 --- a/HwProj.Common/HwProj.Models/AuthService/DTO/TokenCredentials.cs +++ b/HwProj.Common/HwProj.Models/AuthService/DTO/TokenCredentials.cs @@ -1,7 +1,10 @@ -namespace HwProj.Models.AuthService.DTO +using System; + +namespace HwProj.Models.AuthService.DTO { public class TokenCredentials { public string AccessToken { get; set; } + public DateTime ExpiresIn { get; set; } } } diff --git a/hwproj.front/package-lock.json b/hwproj.front/package-lock.json index 3aa81f362..c83a317ce 100644 --- a/hwproj.front/package-lock.json +++ b/hwproj.front/package-lock.json @@ -4975,6 +4975,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/api": "6.5.16", @@ -5002,12 +5003,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/api": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/channels": "6.5.16", @@ -5041,6 +5044,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/builder-webpack4": { @@ -5439,6 +5443,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5498,6 +5503,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5512,6 +5518,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.16.tgz", "integrity": "sha512-LzBOFJKITLtDcbW9jXl0/PaG+4xAz25PK8JxPZpIALbmOpYWOAPcO6V9C2heX6e6NgWFMUxjplkULEk9RCQMNA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -5536,6 +5543,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/core": { @@ -5702,6 +5710,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2" @@ -5802,6 +5811,7 @@ "version": "0.0.2--canary.4566f4d.1", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.15" @@ -6274,6 +6284,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6295,12 +6306,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, "license": "ISC", "dependencies": { "core-js": "^3.6.5", @@ -6317,6 +6330,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -6330,6 +6344,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -6342,6 +6357,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -6357,6 +6373,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -6440,6 +6457,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6460,6 +6478,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/ui": { @@ -7086,6 +7105,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.3.tgz", "integrity": "sha512-/CLhCW79JUeLKznI6mbVieGbl4QU5Hfn+6udw1YHZoofASjbQ5zaP5LzAUZYDpRYEjS4/P+DhEgyJ/PQmGGTWw==", + "dev": true, "license": "MIT" }, "node_modules/@types/isomorphic-fetch": { @@ -7454,6 +7474,7 @@ "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "dev": true, "license": "MIT" }, "node_modules/@types/webpack-sources": { @@ -18001,6 +18022,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true, "license": "MIT" }, "node_modules/is-generator-function": { @@ -18123,6 +18145,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18200,6 +18223,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18671,13 +18695,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", - "peer": true - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -19317,6 +19334,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true, "license": "MIT" }, "node_modules/map-visit": { @@ -20509,6 +20527,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, "license": "MIT", "dependencies": { "map-or-similar": "^1.5.0" @@ -20565,21 +20584,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -22490,6 +22494,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -22692,6 +22697,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -22844,18 +22850,6 @@ "node": ">=6" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portable-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/portable-fetch/-/portable-fetch-3.0.0.tgz", @@ -27150,6 +27144,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -28026,6 +28021,7 @@ "version": "2.14.4", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", + "dev": true, "license": "MIT" }, "node_modules/stream-browserify": { @@ -28659,6 +28655,7 @@ "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", + "dev": true, "license": "MIT", "dependencies": { "@types/is-function": "^1.0.0", @@ -28675,6 +28672,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -29196,6 +29194,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -30088,6 +30087,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/util.promisify": { @@ -31943,21 +31943,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 359f29fa3..7c5dfc1a4 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -47,7 +47,7 @@ const withRouter = (Component: any) => { ); }; }; - +`` class App extends Component<{ navigate: any }, AppState> { constructor(props: { navigate: any }) { super(props); From 610efbe6d4210bf11a96d2e1fc07df850ef44be6 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 27 Nov 2025 14:41:42 +0300 Subject: [PATCH 02/15] fix: changing the token storage from localStorage to HttpSecure cookie --- .../Controllers/AccountController.cs | 36 +- .../HwProj.APIGateway.API/Startup.cs | 38 +- .../Controllers/AccountController.cs | 11 + .../Extensions/MappingExtensions.cs | 8 + .../Services/AccountService.cs | 14 + .../Services/IAccountService.cs | 1 + .../AuthServiceClient.cs | 10 + .../IAuthServiceClient.cs | 1 + .../AuthService/DTO/AccountSummaryDto.cs | 19 + generate-swagger-client.ps1 | Bin 4260 -> 7262 bytes hwproj.front/src/App.tsx | 27 +- hwproj.front/src/AuthLayout.tsx | 2 +- hwproj.front/src/api/ApiSingleton.ts | 35 +- hwproj.front/src/api/api.ts | 849 +++--------------- hwproj.front/src/components/Auth/Login.tsx | 2 +- .../src/components/Courses/Course.tsx | 3 +- .../Courses/Statistics/StudentStatsChart.tsx | 2 +- .../src/components/Experts/AuthLayout.tsx | 2 +- hwproj.front/src/services/AuthService.ts | 157 ++-- hwproj.front/vite.config.ts | 4 +- 20 files changed, 369 insertions(+), 852 deletions(-) create mode 100644 HwProj.Common/HwProj.Models/AuthService/DTO/AccountSummaryDto.cs diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs index 879b45833..8f8e636ad 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs @@ -90,6 +90,15 @@ public async Task GetUserData() return Ok(aggregatedResult); } + [HttpGet("getUserSummary")] + [Authorize] + [ProducesResponseType(typeof(AccountSummaryDto), (int)HttpStatusCode.OK)] + public async Task GetUserSummary() + { + var accountData = await AuthServiceClient.GetAccountSummary(UserId); + return Ok(accountData); + } + [HttpPost("register")] [ProducesResponseType(typeof(Result), (int)HttpStatusCode.OK)] public async Task Register(RegisterViewModel model) @@ -99,14 +108,13 @@ public async Task Register(RegisterViewModel model) } [HttpPost("login")] - [ProducesResponseType(typeof(Result), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.OK)] public async Task Login(LoginViewModel model) { var tokenMeta = await AuthServiceClient.Login(model).ConfigureAwait(false); if (!tokenMeta.Succeeded) { ClearTokenCookie(); - //return BadRequest(tokenMeta); return Unauthorized(); } @@ -119,23 +127,22 @@ public async Task Login(LoginViewModel model) SameSite = SameSiteMode.Strict }); - if (string.IsNullOrEmpty(tokenMeta.Value.RefreshToken)) - { - RefreshToken(); - } - - // var antiForgeryToken = ); - - return Ok( tokenMeta.Succeeded ); + return Ok(); } [Authorize] [HttpGet("refreshToken")] - [ProducesResponseType(typeof(Result), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.OK)] public async Task RefreshToken() { var tokenMeta = await AuthServiceClient.RefreshToken(UserId!); - Response.Cookies.Append("refreshToken", tokenMeta.Value.AccessToken, + if (!tokenMeta.Succeeded) + { + ClearTokenCookie(); + return Unauthorized(); + } + + Response.Cookies.Append("accessToken", tokenMeta.Value.AccessToken, new CookieOptions { Expires = tokenMeta.Value.ExpiresIn, @@ -143,11 +150,11 @@ public async Task RefreshToken() Secure = true, SameSite = SameSiteMode.Strict }); - return Ok(tokenMeta.Succeeded); + + return Ok(); } [HttpPost("logout")] - [AllowAnonymous] public IActionResult Logout() { ClearTokenCookie(); @@ -159,7 +166,6 @@ private void ClearTokenCookie() if (Request.Cookies.ContainsKey("accessToken")) { Response.Cookies.Delete("accessToken"); - Response.Cookies.Delete("refreshToken"); } } diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index d7002eb2f..5ce791434 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -85,16 +85,7 @@ public void ConfigureServices(IServiceCollection services) } return Task.CompletedTask; - }, - - //? - OnAuthenticationFailed = context => - { - if (context.Exception is SecurityTokenExpiredException){ - context.Response.Headers.Add("Token-Expired", "true"); } - return Task.CompletedTask; - } }; }); @@ -118,13 +109,13 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env) app.UseHsts(); app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); app.UseCors(x => x .AllowAnyMethod() .AllowAnyHeader() - .SetIsOriginAllowed(_ => true) // Configuration["Cors:AllowOrigins "]).Split(';'); + .SetIsOriginAllowed(_ => true) .AllowCredentials()); + app.UseAuthentication(); + app.UseAuthorization(); app.UseEndpoints(x => x.MapControllers()); } @@ -139,29 +130,6 @@ private static void ConfigureHwProjServiceSwaggerGen(IServiceCollection services var actionName = apiDesc.ActionDescriptor.RouteValues["action"]; return $"{controllerName}{actionName}"; }); - c.AddSecurityDefinition("Bearer", - new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Please enter into field the word 'Bearer' following by space and JWT", - Name = "Authorization", - Type = SecuritySchemeType.ApiKey - }); - c.AddSecurityRequirement( - new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Id = "Bearer", - Type = ReferenceType.SecurityScheme - } - }, - new List() - } - }); }); } } diff --git a/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs b/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs index 27b10e067..1d1e28d4a 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs @@ -47,6 +47,17 @@ public async Task GetUserDataById(string userId) : NotFound(); } + [HttpGet("getUserSummary/{userId}")] + [ProducesResponseType(typeof(AccountSummaryDto), (int)HttpStatusCode.OK)] + public async Task GetUserSummaryById(string userId) + { + var accountSummary = await _accountService.GetAccountSummaryAsync(userId).ConfigureAwait(false); + + return accountSummary != null + ? Ok(accountSummary) as IActionResult + : NotFound(); + } + [HttpGet("getUserDataByEmail/{email}")] [ProducesResponseType(typeof(AccountDataDto), (int)HttpStatusCode.OK)] public async Task GetUserDataByEmail(string email) diff --git a/HwProj.AuthService/HwProj.AuthService.API/Extensions/MappingExtensions.cs b/HwProj.AuthService/HwProj.AuthService.API/Extensions/MappingExtensions.cs index b9fb1d328..a259a09bf 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Extensions/MappingExtensions.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Extensions/MappingExtensions.cs @@ -19,5 +19,13 @@ public static AccountDataDto ToAccountDataDto(this User user, string role) user.CompanyName, user.Bio); } + + public static AccountSummaryDto ToAccountSummaryDto(this User user, string role) + { + return new AccountSummaryDto( + user.Id, + user.Email, + role); + } } } diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs index 9a7c700a7..0a125c7fa 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs @@ -60,12 +60,26 @@ private async Task GetAccountDataAsync(User user) return user.ToAccountDataDto(userRole); } + private async Task GetAccountSummaryAsync(User user) + { + if (user == null) return null; + var userRoles = await _userManager.GetRolesAsync(user); + var userRole = userRoles.FirstOrDefault() ?? Roles.StudentRole; + return user.ToAccountSummaryDto(userRole); + } + public async Task GetAccountDataAsync(string userId) { var user = await _userManager.FindByIdAsync(userId); return await GetAccountDataAsync(user); } + public async Task GetAccountSummaryAsync(string userId) + { + var user = await _userManager.FindByIdAsync(userId); + return await GetAccountSummaryAsync(user); + } + public async Task GetAccountsDataAsync(string[] userIds) { var users = await _aspUserManager.Users diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/IAccountService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/IAccountService.cs index ca7d3c1d4..c953f99ba 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/IAccountService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/IAccountService.cs @@ -11,6 +11,7 @@ namespace HwProj.AuthService.API.Services public interface IAccountService { Task GetAccountDataAsync(string userId); + Task GetAccountSummaryAsync(string userId); Task GetAccountsDataAsync(string[] userIds); Task GetAccountDataByEmailAsync(string email); Task> RegisterUserAsync(RegisterDataDTO model); diff --git a/HwProj.AuthService/HwProj.AuthService.Client/AuthServiceClient.cs b/HwProj.AuthService/HwProj.AuthService.Client/AuthServiceClient.cs index bf2d29112..11b9c8ea0 100644 --- a/HwProj.AuthService/HwProj.AuthService.Client/AuthServiceClient.cs +++ b/HwProj.AuthService/HwProj.AuthService.Client/AuthServiceClient.cs @@ -33,6 +33,16 @@ public async Task GetAccountData(string userId) return await response.DeserializeAsync().ConfigureAwait(false); } + public async Task GetAccountSummary(string userId) + { + using var httpRequest = new HttpRequestMessage( + HttpMethod.Get, + _authServiceUri + $"api/account/getUserSummary/{userId}"); + + var response = await _httpClient.SendAsync(httpRequest); + return await response.DeserializeAsync().ConfigureAwait(false); + } + public async Task GetAccountDataByEmail(string email) { using var httpRequest = new HttpRequestMessage( diff --git a/HwProj.AuthService/HwProj.AuthService.Client/IAuthServiceClient.cs b/HwProj.AuthService/HwProj.AuthService.Client/IAuthServiceClient.cs index 63d678991..af454342e 100644 --- a/HwProj.AuthService/HwProj.AuthService.Client/IAuthServiceClient.cs +++ b/HwProj.AuthService/HwProj.AuthService.Client/IAuthServiceClient.cs @@ -9,6 +9,7 @@ namespace HwProj.AuthService.Client public interface IAuthServiceClient { Task GetAccountData(string userId); + Task GetAccountSummary(string userId); Task GetAccountDataByEmail(string email); Task GetAccountsData(string[] userId); Task> Register(RegisterViewModel model); diff --git a/HwProj.Common/HwProj.Models/AuthService/DTO/AccountSummaryDto.cs b/HwProj.Common/HwProj.Models/AuthService/DTO/AccountSummaryDto.cs new file mode 100644 index 000000000..118d6004a --- /dev/null +++ b/HwProj.Common/HwProj.Models/AuthService/DTO/AccountSummaryDto.cs @@ -0,0 +1,19 @@ +namespace HwProj.Models.AuthService.DTO +{ + public class AccountSummaryDto + { + public string UserId { get; set; } + public string Email { get; } + public string Role { get; } + + public AccountSummaryDto( + string userId, + string email, + string role) + { + UserId = userId; + Email = email; + Role = role; + } + } +} \ No newline at end of file diff --git a/generate-swagger-client.ps1 b/generate-swagger-client.ps1 index 274c42f93e6c7f0468acdd40df5a5ac39a381434..ab946ebb787f095261f136b9a39740d2bc916aa2 100644 GIT binary patch literal 7262 zcmcJU+jG-K6vp>KPo3#Y=}i05L72=mK;?v7z`58-a4rNwLQ6WlFhjwl;5KoLV?t@m zzuxxu?P}4=mK>)fld&aByL)cm`OewRzyC~_*XF=<`1H&R(>KRlf97*&VpHc+F>iR< zH(hgL7R?lYJ;rt!ciU9W5pz!M{HmS#n)lhW8Gf~SKE>SkjC^fn_4wW5=_}?r*;A}B zJ2Wa87^UBbtT)Bjs+s0<$JEB2mE>ks&2Q+_!@4ic7iMBWzhe8H^@nKQV(uY+Ic8oT zugs&3{)X*5#Y=-vCHsWAolnw-Be2Q)L+hbl8ixlwX)`A=kCEO-dW_zW%@h7wSkfjY zee)Bej=3ssd7lq@v%(l>dy(`u(hn|4^0PBE^Kl*>v19WNJG=J#^1b!UqNRn`+ZOL0 zH!skjZPxLXkf8V|Ho|=8e|zr@xzZ(~$Ka%DezkWUR+Zm+#9F#Ys?$L+Y%?l0lVGEh zMt{=mr#gjr3Ga*YKJ?=abAvVivKWcE)8A8zvpy>)=01{SvygTzYD9OVxoA3?kCvm@ ztZ=syEk;f5=c1)(kLxV&>U>stcaQP9R+T8b_mRGX1Z8{{*Yo&ME9GOf z`HA(Z!`(dBDxSJVG^eAjsK!$t<@soq zI2OgQpGG8)9v|UtPN&6mxNK8_7jD_57Tq5!_^?dAIMgn_Nc9zN7lP1AX&L_ zV<7LmKAPEPR!rpzJ?8R4HXY#W6TSy6D)Z&I=yQIQh0SOi+d~XGuFG=sI?}I4w;44D zCYKqz7yZDqExe-Gcm|s0UUfcW?&9_H8G0TTEvrN4$rf%n|u8i(z6vHwM z7HPFc_q2`1dF?{qsz*HKBR+EdVlf|ihKbEDxtz(@AZ1LFa$4S!jL+0{_fzkjfLhRz`Iy4Vb= zjIg5uOO5VA#$n7lm|K7sYH06yp=s~MsZ01IUk`=anyq@F?{-*0BUfyF+~K|Bq%z*j zb5)EEsuwxkr7Scr5Tf#HTVuJ(V<9F1Zz4KUf7HCpPY7Sujcq zUq>G1D*Bd1U1FNH^VNfTH1hL}XXW{*1c!eUyL{3SW&I=aEi z;``gIwy8Jlu$u3*6MnyC)e5!mPo8$U8%9nTt1gl|*ptv3GG#yyqEV!sfm@-cT+l8#b36GtAlSQy#8z zr+y+f+MNvMYnYcCK|J*Qd~6W%isR&rSEQR{>wC*;=4zvtxzoK5Qfbu1^E|RuziYdR7MKdVT46n=BY472m8#6#8M~f3 z9j&HrNYcIJQ5KfHOKipJvV&FMp-TQenjO(Es2g!-Y_~XJyJ=6`ZZYa4y(`jxlNDS~ zuCuvY=~>-TI-_!?%@wnReC=EFS`>RLlNNuHq>iUZv(U3nN>SWqY}~#k7nVNp(IS4Y zN9%Zemb#%jsD5~t$TWzj?)6?L5<6TQmlPBIf1rbvZO$sxH)f}Ai`Kh0=}5~)WIi3W z3MVV2D>z1fXZyngtA+kQ^2$c5$dCGe1YCwUR>7%GX2Sk{g{KXBR>^{56ZgBIvDWxFcAJG0Wst2#E%Uc+_AcY&&9zSepFg_X_squDOBtZH6Cw$99E h_*}ySWqM26oIUY%qqT4L=K(^uxL^iW_>ns={Xf>GD6#+m literal 4260 zcmb7HZEw?76#kxHab_a5W!Pz9n@|xS5TsktD5x#!mq=io+r(XB2iplSsMHo1L#>AP zB5hwLHjN)pLOTj+!(X`mM|;k-o!Cy)uKkcEw(q^?Ip=wvb6l(mM|fu7dbhm>T_&v_ z1TBBIRPsd2oiUnbM>vM-)k^By`I7CH{0C;OCcKj6Rs~jQQx zaojCEx@D}Ho>nNv+og`j=?&L18+F$YX1|=Co=!F?b!L*U#+vUsSUm`T&TUkM2L#t$ z+tF{CL7j@XmX<8fZUu|38;}+slmw|!dbQmUr&wFb?ZtqzsN8nYY6qtds$zOp-R=k= zGh6mG;Rm*32Da-Q7hL^8%X8O^RnK*Titkw!EY%7HdzB`22goOvu}_qeI5D}bO-<3` z0^x7Pvjd@jgS?X_|CW9EUhc_Vxi1e%e!`be@?-dvaIF`oy|HKrpti!z@TvOTlHKqr;BFa&+AN>LT z05GGRa$e`1j{BX^SH!(Valegfkbc{Jl8|f|CIaKNmacE^qb)hVcQ5Ss?0y* zjLlMUvUtZRKeR-PYh>IIzHipVR73!dxctx#Xhs28FRYVj_+kvvPA)@)0f7MHOCa0? z-QrqkOqDYv|B-Kjat{SL#O+t-D15=39MD$oQY@{p+Z-pBw3Ns4=wzkrq7Kg^vJ4pV ze-x5FIwuccPIKS~dBBqVD0?^t_+KcL(g~E9xGp#}6rIXoZXu zj2?95)_m?Tg=@|1n54tka~!6-iUhIk2-3m*l<(^e3J|KV%vxU+OivE zJ*X{XqbQ?1fWM@H?qD$<`=o4WC)RN+t%&@8;RwimK(YfRdvT&%V?sIXx z49$Q9zbC;mgD^`dpWa|YB-?kJuGgyDR@|YOVN_nap{v%?Rdr?LJc7Vn)vnn%(!X@P zU^Prj(1hJ=xgG}h&nsu~Vfy$Oo;AnK5LYnVH}Ov!-ie~nE=`u>=# { @@ -52,21 +53,32 @@ class App extends Component<{ navigate: any }, AppState> { constructor(props: { navigate: any }) { super(props); this.state = { - loggedIn: ApiSingleton.authService.isLoggedIn(), + loggedIn: ApiSingleton.authService.loggedIn(), isLecturer: ApiSingleton.authService.isLecturer(), isExpert: ApiSingleton.authService.isExpert(), newNotificationsCount: 0, - appBarContextAction: "Default" + appBarContextAction: "Default", + authReady: false, }; appBarStateManager.setOnContextActionChange(appBarState => this.setState({appBarContextAction: appBarState})) } componentDidMount = async () => { + const user = await ApiSingleton.authService.getProfile(); + this.setState( + { + loggedIn: !!user, + isLecturer: ApiSingleton.authService.isLecturer(), + isExpert: ApiSingleton.authService.isExpert(), + authReady: true, + } + ); + await this.updatedNewNotificationsCount() } updatedNewNotificationsCount = async () => { - if (ApiSingleton.authService.isLoggedIn()) { + if (ApiSingleton.authService.loggedIn()) { const data = await ApiSingleton.notificationsApi.notificationsGetNewNotificationsCount() this.setState({newNotificationsCount: data}) } @@ -86,13 +98,14 @@ class App extends Component<{ navigate: any }, AppState> { } } - logout = () => { - ApiSingleton.authService.logout(); - this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0}); + logout = async () => { + await ApiSingleton.authService.logout(); + this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0, authReady: true}); this.props.navigate("/login"); } render() { + if (!this.state.authReady) {return null;} return ( <>
- ApiSingleton.authService.isLoggedIn() + ApiSingleton.authService.loggedIn() ? : diff --git a/hwproj.front/src/api/ApiSingleton.ts b/hwproj.front/src/api/ApiSingleton.ts index df3527ba0..0f919e79e 100644 --- a/hwproj.front/src/api/ApiSingleton.ts +++ b/hwproj.front/src/api/ApiSingleton.ts @@ -14,7 +14,6 @@ import AuthService from "../services/AuthService"; import CustomFilesApi from "./CustomFilesApi"; class Api { - auth = new AuthService() readonly accountApi: AccountApi; readonly expertsApi: ExpertsApi; readonly coursesApi: CoursesApi; @@ -70,22 +69,34 @@ function getApiBase(): string { return `${protocol}//${hostname}${effectivePort ? `:${effectivePort}` : ""}` } +const cookieFetch = async (url: string, init: any) => { + const response = await fetch(url, { + ...init, + credentials: "include" + }); + + return response; +} + const basePath = getApiBase() const authService = new AuthService() +const apiConfig = { + basePath: basePath, +} let ApiSingleton: Api; ApiSingleton = new Api( - new AccountApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new ExpertsApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new CoursesApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new SolutionsApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new NotificationsApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new HomeworksApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new TasksApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new StatisticsApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new SystemApi({basePath: basePath}), + new AccountApi(apiConfig, undefined, cookieFetch), + new ExpertsApi(apiConfig, undefined, cookieFetch), + new CoursesApi(apiConfig, undefined, cookieFetch), + new SolutionsApi(apiConfig, undefined, cookieFetch), + new NotificationsApi(apiConfig, undefined, cookieFetch), + new HomeworksApi(apiConfig, undefined, cookieFetch), + new TasksApi(apiConfig, undefined, cookieFetch), + new StatisticsApi(apiConfig, undefined, cookieFetch), + new SystemApi(apiConfig, undefined, cookieFetch), authService, - new CustomFilesApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}), - new FilesApi({basePath: basePath, apiKey: () => "Bearer " + authService.getToken()!}) + new CustomFilesApi(apiConfig, undefined, cookieFetch), + new FilesApi(apiConfig, undefined, cookieFetch) ); export default ApiSingleton; \ No newline at end of file diff --git a/hwproj.front/src/api/api.ts b/hwproj.front/src/api/api.ts index 0d252a834..fd10de9ac 100644 --- a/hwproj.front/src/api/api.ts +++ b/hwproj.front/src/api/api.ts @@ -1,4 +1,4 @@ -/// +/// // tslint:disable /** * API Gateway @@ -144,6 +144,31 @@ export interface AccountDataDto { */ companyName?: string; } +/** + * + * @export + * @interface AccountSummaryDto + */ +export interface AccountSummaryDto { + /** + * + * @type {string} + * @memberof AccountSummaryDto + */ + userId?: string; + /** + * + * @type {string} + * @memberof AccountSummaryDto + */ + email?: string; + /** + * + * @type {string} + * @memberof AccountSummaryDto + */ + role?: string; +} /** * * @export @@ -2526,6 +2551,12 @@ export interface TokenCredentials { * @memberof TokenCredentials */ accessToken?: string; + /** + * + * @type {Date} + * @memberof TokenCredentials + */ + expiresIn?: Date; } /** * @@ -2803,14 +2834,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (code !== undefined) { localVarQueryParameter['code'] = code; } @@ -2838,14 +2861,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -2872,14 +2887,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -2903,14 +2910,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -2937,14 +2936,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -2973,13 +2964,27 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } + localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + localVarUrlObj.search = null; + localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers); + + return { + url: url.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountGetUserSummary(options: any = {}): FetchArgs { + const localVarPath = `/api/Account/getUserSummary`; + const localVarUrlObj = url.parse(localVarPath, true); + const localVarRequestOptions = Object.assign({ method: 'GET' }, options); + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 @@ -3004,14 +3009,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3039,14 +3036,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3061,6 +3050,28 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountLogout(options: any = {}): FetchArgs { + const localVarPath = `/api/Account/logout`; + const localVarUrlObj = url.parse(localVarPath, true); + const localVarRequestOptions = Object.assign({ method: 'POST' }, options); + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + localVarUrlObj.search = null; + localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers); + + return { + url: url.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {*} [options] Override http request option. @@ -3073,14 +3084,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3104,14 +3107,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3139,14 +3134,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3174,14 +3161,6 @@ export const AccountApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3311,6 +3290,23 @@ export const AccountApiFp = function(configuration?: Configuration) { }); }; }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountGetUserSummary(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { + const localVarFetchArgs = AccountApiFetchParamCreator(configuration).accountGetUserSummary(options); + return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => { + return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } else { + throw response; + } + }); + }; + }, /** * * @param {InviteLecturerViewModel} [body] @@ -3335,12 +3331,12 @@ export const AccountApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - accountLogin(body?: LoginViewModel, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { + accountLogin(body?: LoginViewModel, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { const localVarFetchArgs = AccountApiFetchParamCreator(configuration).accountLogin(body, options); return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => { return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => { if (response.status >= 200 && response.status < 300) { - return response.json(); + return response; } else { throw response; } @@ -3352,12 +3348,29 @@ export const AccountApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - accountRefreshToken(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { + accountLogout(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { + const localVarFetchArgs = AccountApiFetchParamCreator(configuration).accountLogout(options); + return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => { + return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => { + if (response.status >= 200 && response.status < 300) { + return response; + } else { + throw response; + } + }); + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountRefreshToken(options?: any): (fetch?: FetchAPI, basePath?: string) => Promise { const localVarFetchArgs = AccountApiFetchParamCreator(configuration).accountRefreshToken(options); return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => { return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => { if (response.status >= 200 && response.status < 300) { - return response.json(); + return response; } else { throw response; } @@ -3479,6 +3492,14 @@ export const AccountApiFactory = function (configuration?: Configuration, fetch? accountGetUserDataById(userId: string, options?: any) { return AccountApiFp(configuration).accountGetUserDataById(userId, options)(fetch, basePath); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountGetUserSummary(options?: any) { + return AccountApiFp(configuration).accountGetUserSummary(options)(fetch, basePath); + }, /** * * @param {InviteLecturerViewModel} [body] @@ -3497,6 +3518,14 @@ export const AccountApiFactory = function (configuration?: Configuration, fetch? accountLogin(body?: LoginViewModel, options?: any) { return AccountApiFp(configuration).accountLogin(body, options)(fetch, basePath); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + accountLogout(options?: any) { + return AccountApiFp(configuration).accountLogout(options)(fetch, basePath); + }, /** * * @param {*} [options] Override http request option. @@ -3606,6 +3635,16 @@ export class AccountApi extends BaseAPI { return AccountApiFp(this.configuration).accountGetUserDataById(userId, options)(this.fetch, this.basePath); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AccountApi + */ + public accountGetUserSummary(options?: any) { + return AccountApiFp(this.configuration).accountGetUserSummary(options)(this.fetch, this.basePath); + } + /** * * @param {InviteLecturerViewModel} [body] @@ -3628,6 +3667,16 @@ export class AccountApi extends BaseAPI { return AccountApiFp(this.configuration).accountLogin(body, options)(this.fetch, this.basePath); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AccountApi + */ + public accountLogout(options?: any) { + return AccountApiFp(this.configuration).accountLogout(options)(this.fetch, this.basePath); + } + /** * * @param {*} [options] Override http request option. @@ -3703,14 +3752,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -3744,14 +3785,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -3790,14 +3823,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3826,14 +3851,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3862,14 +3879,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3898,14 +3907,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3934,14 +3935,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -3977,14 +3970,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (userId !== undefined) { localVarQueryParameter['userId'] = userId; } @@ -4024,14 +4009,6 @@ export const CourseGroupsApiFetchParamCreator = function (configuration?: Config const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -4471,14 +4448,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4513,14 +4482,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4544,14 +4505,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -4584,14 +4537,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4627,14 +4572,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -4667,14 +4604,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4697,14 +4626,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4733,14 +4654,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4763,14 +4676,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4799,14 +4704,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4830,14 +4727,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (programName !== undefined) { localVarQueryParameter['programName'] = programName; } @@ -4870,14 +4759,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4912,14 +4793,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4942,14 +4815,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -4984,14 +4849,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -5020,14 +4877,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -5057,14 +4906,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -5104,14 +4945,6 @@ export const CoursesApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -5875,14 +5708,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -5905,14 +5730,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -5936,14 +5753,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (expertEmail !== undefined) { localVarQueryParameter['expertEmail'] = expertEmail; } @@ -5971,14 +5780,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6006,14 +5807,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6041,14 +5834,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6075,14 +5860,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -6106,14 +5883,6 @@ export const ExpertsApiFetchParamCreator = function (configuration?: Configurati const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6471,14 +6240,6 @@ export const FilesApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (fileId !== undefined) { localVarQueryParameter['fileId'] = fileId; } @@ -6511,14 +6272,6 @@ export const FilesApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -6542,14 +6295,6 @@ export const FilesApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6582,14 +6327,6 @@ export const FilesApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -6618,14 +6355,6 @@ export const FilesApiFetchParamCreator = function (configuration?: Configuration const localVarQueryParameter = {} as any; const localVarFormParams = new URLSearchParams(); - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (filesScopeCourseId !== undefined) { localVarFormParams.set('FilesScope.CourseId', filesScopeCourseId as any); } @@ -6919,14 +6648,6 @@ export const HomeworksApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -6959,14 +6680,6 @@ export const HomeworksApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -6995,14 +6708,6 @@ export const HomeworksApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7031,14 +6736,6 @@ export const HomeworksApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7068,14 +6765,6 @@ export const HomeworksApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -7334,14 +7023,6 @@ export const NotificationsApiFetchParamCreator = function (configuration?: Confi const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -7368,14 +7049,6 @@ export const NotificationsApiFetchParamCreator = function (configuration?: Confi const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7398,14 +7071,6 @@ export const NotificationsApiFetchParamCreator = function (configuration?: Confi const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7428,14 +7093,6 @@ export const NotificationsApiFetchParamCreator = function (configuration?: Confi const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7459,14 +7116,6 @@ export const NotificationsApiFetchParamCreator = function (configuration?: Confi const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -7715,14 +7364,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7747,14 +7388,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (taskId !== undefined) { localVarQueryParameter['taskId'] = taskId; } @@ -7791,14 +7424,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7827,14 +7452,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7869,14 +7486,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -7906,14 +7515,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (secondMentorId !== undefined) { localVarQueryParameter['secondMentorId'] = secondMentorId; } @@ -7941,14 +7542,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - if (taskId !== undefined) { localVarQueryParameter['taskId'] = taskId; } @@ -7981,14 +7574,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -8017,14 +7602,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -8054,14 +7631,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -8095,14 +7664,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -8136,14 +7697,6 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -8685,14 +8238,6 @@ export const StatisticsApiFetchParamCreator = function (configuration?: Configur const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -8721,14 +8266,6 @@ export const StatisticsApiFetchParamCreator = function (configuration?: Configur const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -8757,14 +8294,6 @@ export const StatisticsApiFetchParamCreator = function (configuration?: Configur const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -8936,14 +8465,6 @@ export const SystemApiFetchParamCreator = function (configuration?: Configuratio const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9037,14 +8558,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -9072,14 +8585,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -9113,14 +8618,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); @@ -9153,14 +8650,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9189,14 +8678,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9219,14 +8700,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9255,14 +8728,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9291,14 +8756,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 localVarUrlObj.search = null; @@ -9328,14 +8785,6 @@ export const TasksApiFetchParamCreator = function (configuration?: Configuration const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - // authentication Bearer required - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? configuration.apiKey("Authorization") - : configuration.apiKey; - localVarHeaderParameter["Authorization"] = localVarApiKeyValue; - } - localVarHeaderParameter['Content-Type'] = 'application/json'; localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query); diff --git a/hwproj.front/src/components/Auth/Login.tsx b/hwproj.front/src/components/Auth/Login.tsx index 1f9e7a3ce..c1caa68f3 100644 --- a/hwproj.front/src/components/Auth/Login.tsx +++ b/hwproj.front/src/components/Auth/Login.tsx @@ -51,7 +51,7 @@ const Login: FC = (props) => { email: '', password: '', error: [], - isLogin: ApiSingleton.authService.isLoggedIn(), + isLogin: ApiSingleton.authService.loggedIn(), }) // Состояние для ошибки электронной почты diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index c2309e0fe..27fc036c9 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -267,8 +267,7 @@ const Course: React.FC = () => { course && course.mentors!.some(t => t.userId === userId) if (shouldRefreshToken) { - const newToken = await ApiSingleton.accountApi.accountRefreshToken() - newToken.value && ApiSingleton.authService.refreshToken(newToken.value.accessToken!) + await ApiSingleton.authService.refreshAccessToken(); return } diff --git a/hwproj.front/src/components/Courses/Statistics/StudentStatsChart.tsx b/hwproj.front/src/components/Courses/Statistics/StudentStatsChart.tsx index 10b7720ea..7c80c56b9 100644 --- a/hwproj.front/src/components/Courses/Statistics/StudentStatsChart.tsx +++ b/hwproj.front/src/components/Courses/Statistics/StudentStatsChart.tsx @@ -30,7 +30,7 @@ interface IStudentStatsChartState { const StudentStatsChart: React.FC = () => { const {courseId} = useParams(); - const isLoggedIn = ApiSingleton.authService.isLoggedIn(); + const isLoggedIn = ApiSingleton.authService.loggedIn(); const [selectedStudents, setSelectedStudents] = useState([]); const [state, setState] = useState({ isFound: false, diff --git a/hwproj.front/src/components/Experts/AuthLayout.tsx b/hwproj.front/src/components/Experts/AuthLayout.tsx index ebfb92c9b..ecb1665a2 100644 --- a/hwproj.front/src/components/Experts/AuthLayout.tsx +++ b/hwproj.front/src/components/Experts/AuthLayout.tsx @@ -24,7 +24,7 @@ const ExpertAuthLayout: FC = (props: IExpertAuthLayoutPr if (!isExpired) { const isExpertLoggedIn = await ApiSingleton.expertsApi.expertsLogin(credentials) if (isExpertLoggedIn.succeeded) { - ApiSingleton.authService.setToken(token!); + await ApiSingleton.authService.getUser(); setIsTokenValid(true); props.onLogin(); diff --git a/hwproj.front/src/services/AuthService.ts b/hwproj.front/src/services/AuthService.ts index be0770392..cd4daa6db 100644 --- a/hwproj.front/src/services/AuthService.ts +++ b/hwproj.front/src/services/AuthService.ts @@ -2,6 +2,13 @@ import {LoginViewModel, AccountApi, RegisterViewModel} from '../api'; import ApiSingleton from "../api/ApiSingleton"; import decode from "jwt-decode"; + +interface User { + id: string; + email: string; + role: string; +} + interface TokenPayload { _userName: string; _id: string; @@ -13,25 +20,50 @@ interface TokenPayload { } export default class AuthService { - client = new AccountApi(); + private _user: User | null = null; constructor() { this.login = this.login.bind(this); this.getProfile = this.getProfile.bind(this); } + public async getUser(): Promise { + try { + const accountData = await ApiSingleton.accountApi.accountGetUserSummary(); + if (accountData) { + this._user = { + id: accountData.userId!, + email: accountData.email!, + role: accountData.role!, + }; + } else { + this._user = null; + } + } + catch (e) { + this._user = null; + } + } + async login(user: LoginViewModel) { - const token = await ApiSingleton.accountApi.accountLogin(user) - if (token.errors) { + try { + await ApiSingleton.accountApi.accountLogin(user); + await this.getUser(); + return { - error: token.errors, - isLogin: false + error: null, + isLogin: true } } - this.setToken(token.value?.accessToken!) - return { - error: null, - isLogin: true + catch (e: any) { + this._user = null; + + const status = e?.status ?? e?.response?.status; + const message = status === 401 ? "Неверный логин или пароль" : "Ошибка авторизации"; + return { + error: [message], + isLogin: false + } } } @@ -43,95 +75,70 @@ export default class AuthService { } } - isLoggedIn() { - const token = this.getToken(); - return !!token && !this.isTokenExpired(token); - } + loggedIn = () => this._user !== null; - isTokenExpired(token: any) { + async refreshAccessToken(){ try { - let decoded = decode(token); - return decoded.exp < Date.now() / 1000; - } catch (err) { - return true; + await ApiSingleton.accountApi.accountRefreshToken(); + await this.getUser(); + window.location.reload(); + } + catch { + await this.logout(); } } - getTokenExpirationDate(token: any) { - const decoded = decode(token); - const expirationDate = new Date(decoded.exp * 1000); - - return expirationDate.toLocaleDateString('ru-RU', { - day: 'numeric', - month: 'long', - year: 'numeric', - }); - } - - setToken = (idToken: string) => localStorage.setItem("id_token", idToken); - - refreshToken = (idToken: string) => { - this.setToken(idToken) - window.location.reload() - } - - getToken = () => localStorage.getItem("id_token"); + public async getProfile(): Promise { + await this.getUser(); - logout = () => localStorage.clear(); - - getProfile = () => { - let result = decode(this.getToken() as string); - if (result.exp < 1761527002) { - this.logout() - alert("Мы обновили кое-что важное, и чтобы все правильно работало, просим вас заново войти в аккаунт! Приносим извинения за неудобства.") - window.location.reload() - } - return result + return this._user; }; - getUserId = () => this.getProfile()._id; + getUserId = () => this._user?.id ?? ""; isExpertProfileEdited = async () => await ApiSingleton.expertsApi.expertsGetIsProfileEdited(); setIsExpertProfileEdited = async () => await ApiSingleton.expertsApi.expertsSetProfileIsEdited(); - loggedIn = () => this.getToken() !== null + getUserEmail = () => this._user?.email; - getUserEmail = () => { - if (this.getToken() === null) { - return "" - } - return this.getProfile()["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] + getRole = () => this._user?.role; + + isMentor() { + const role = this._user?.role; + return role === "Lecturer" || role === "Expert"; } - getRole = () => { - if (this.getToken() === null) { - return null - } + isLecturer = () => this._user?.role === "Lecturer"; - return this.getProfile()["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; - } - - isMentor() { - if (this.getToken() === null) { - return false + isExpert = () => this._user?.role === "Expert"; + + async logout() { + try { + await ApiSingleton.accountApi.accountLogout(); } + catch {} - const role = this.getProfile()["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; - return role === "Lecturer" || role === "Expert"; + this._user = null; } - isLecturer() { - if (this.getToken() === null) { - return false + isTokenExpired(token: any) { + try { + let decoded = decode(token); + return decoded.exp < Date.now() / 1000; + } catch (err) { + return true; } - return this.getProfile()["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"] === "Lecturer" } - isExpert() { - if (this.getToken() === null) { - return false - } - return this.getProfile()["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"] === "Expert" + getTokenExpirationDate(token: any) { + const decoded = decode(token); + const expirationDate = new Date(decoded.exp * 1000); + + return expirationDate.toLocaleDateString('ru-RU', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); } } diff --git a/hwproj.front/vite.config.ts b/hwproj.front/vite.config.ts index a5740710f..daa98f63e 100644 --- a/hwproj.front/vite.config.ts +++ b/hwproj.front/vite.config.ts @@ -31,11 +31,11 @@ export default defineConfig({ server: { host: '0.0.0.0', port: 3000, - allowedHosts: ["hwproj.ru"], + allowedHosts: ["hwproj.ru", "localhost"], hmr: { host: 'localhost', port: 3000, - protocol: 'wss' + protocol: 'ws' }, open: true }, From f36bafd483df60f07354c7b3012b028717eeaa0d Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 27 Nov 2025 14:59:32 +0300 Subject: [PATCH 03/15] fix: small fix --- hwproj.front/src/services/AuthService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hwproj.front/src/services/AuthService.ts b/hwproj.front/src/services/AuthService.ts index cd4daa6db..7c0119217 100644 --- a/hwproj.front/src/services/AuthService.ts +++ b/hwproj.front/src/services/AuthService.ts @@ -27,7 +27,7 @@ export default class AuthService { this.getProfile = this.getProfile.bind(this); } - public async getUser(): Promise { + public async getUser() { try { const accountData = await ApiSingleton.accountApi.accountGetUserSummary(); if (accountData) { @@ -88,7 +88,7 @@ export default class AuthService { } } - public async getProfile(): Promise { + public async getProfile() { await this.getUser(); return this._user; @@ -100,12 +100,12 @@ export default class AuthService { setIsExpertProfileEdited = async () => await ApiSingleton.expertsApi.expertsSetProfileIsEdited(); - getUserEmail = () => this._user?.email; + getUserEmail = () => this._user?.email ?? ""; - getRole = () => this._user?.role; + getRole = () => this._user?.role ?? ""; isMentor() { - const role = this._user?.role; + const role = this._user?.role ?? ""; return role === "Lecturer" || role === "Expert"; } From c51c79d5ee375c53a50ec76f06ccde87b2bbf9d6 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 27 Nov 2025 15:08:55 +0300 Subject: [PATCH 04/15] fix: small fix --- hwproj.front/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hwproj.front/vite.config.ts b/hwproj.front/vite.config.ts index daa98f63e..61248f857 100644 --- a/hwproj.front/vite.config.ts +++ b/hwproj.front/vite.config.ts @@ -31,7 +31,7 @@ export default defineConfig({ server: { host: '0.0.0.0', port: 3000, - allowedHosts: ["hwproj.ru", "localhost"], + allowedHosts: ["hwproj.ru"], hmr: { host: 'localhost', port: 3000, From 03cbaf4af3e18dc7041536469b97b8a578c12f1c Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 27 Nov 2025 18:24:20 +0300 Subject: [PATCH 05/15] fix: small fix --- .../HwProj.APIGateway.API/Startup.cs | 3 ++- .../Models/IdentityContext.cs | 1 - hwproj.front/src/App.tsx | 24 ++++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index 5ce791434..baa6ba88d 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -61,7 +61,7 @@ public void ConfigureServices(IServiceCollection services) }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x => { - x.RequireHttpsMetadata = true; + x.RequireHttpsMetadata = false; x.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = "AuthService", @@ -90,6 +90,7 @@ public void ConfigureServices(IServiceCollection services) }); services.AddHttpClient(); + services.AddHttpContextAccessor(); services.AddAuthServiceClient(); services.AddCoursesServiceClient(); services.AddSolutionServiceClient(); diff --git a/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs b/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs index e7b98f880..5dd93e157 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Models/IdentityContext.cs @@ -6,7 +6,6 @@ namespace HwProj.AuthService.API.Models public sealed class IdentityContext : IdentityDbContext { public DbSet ExpertsData { get; set; } - public IdentityContext(DbContextOptions options) : base(options) { diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 7198f3237..03fda49b7 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -24,6 +24,8 @@ import PasswordRecovery from "components/Auth/PasswordRecovery"; import AuthLayout from "./AuthLayout"; import ExpertAuthLayout from "./components/Experts/AuthLayout"; import TrackPageChanges from "TrackPageChanges"; +import {Alert} from "@mui/material"; +import {Snackbar} from "@material-ui/core"; // TODO: add flux @@ -34,6 +36,7 @@ interface AppState { newNotificationsCount: number; appBarContextAction: AppBarContextAction; authReady: boolean; + snackbarOpen: boolean; } const withRouter = (Component: any) => { @@ -48,7 +51,7 @@ const withRouter = (Component: any) => { ); }; }; -`` + class App extends Component<{ navigate: any }, AppState> { constructor(props: { navigate: any }) { super(props); @@ -59,6 +62,7 @@ class App extends Component<{ navigate: any }, AppState> { newNotificationsCount: 0, appBarContextAction: "Default", authReady: false, + snackbarOpen: localStorage.getItem("cookies") !== "true", }; appBarStateManager.setOnContextActionChange(appBarState => this.setState({appBarContextAction: appBarState})) } @@ -104,6 +108,13 @@ class App extends Component<{ navigate: any }, AppState> { this.props.navigate("/login"); } + closeSnackbar = () => { + localStorage.setItem("cookies", "true"); + this.setState({ + snackbarOpen: false, + }); + } + render() { if (!this.state.authReady) {return null;} return ( @@ -115,6 +126,17 @@ class App extends Component<{ navigate: any }, AppState> { onLogout={this.logout} contextAction={this.state.appBarContextAction}/> + + + Мы используем куки для авторизации + + }> }/> From 1eea4d679020421f9d3736ee8c75fbd056ecc710 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 27 Nov 2025 18:28:10 +0300 Subject: [PATCH 06/15] fix: deleate snackBar --- hwproj.front/src/App.tsx | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 03fda49b7..1583ab806 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -24,8 +24,6 @@ import PasswordRecovery from "components/Auth/PasswordRecovery"; import AuthLayout from "./AuthLayout"; import ExpertAuthLayout from "./components/Experts/AuthLayout"; import TrackPageChanges from "TrackPageChanges"; -import {Alert} from "@mui/material"; -import {Snackbar} from "@material-ui/core"; // TODO: add flux @@ -36,7 +34,6 @@ interface AppState { newNotificationsCount: number; appBarContextAction: AppBarContextAction; authReady: boolean; - snackbarOpen: boolean; } const withRouter = (Component: any) => { @@ -61,8 +58,7 @@ class App extends Component<{ navigate: any }, AppState> { isExpert: ApiSingleton.authService.isExpert(), newNotificationsCount: 0, appBarContextAction: "Default", - authReady: false, - snackbarOpen: localStorage.getItem("cookies") !== "true", + authReady: false }; appBarStateManager.setOnContextActionChange(appBarState => this.setState({appBarContextAction: appBarState})) } @@ -108,13 +104,6 @@ class App extends Component<{ navigate: any }, AppState> { this.props.navigate("/login"); } - closeSnackbar = () => { - localStorage.setItem("cookies", "true"); - this.setState({ - snackbarOpen: false, - }); - } - render() { if (!this.state.authReady) {return null;} return ( @@ -126,17 +115,6 @@ class App extends Component<{ navigate: any }, AppState> { onLogout={this.logout} contextAction={this.state.appBarContextAction}/> - - - Мы используем куки для авторизации - - }> }/> From 5e5205052bf3811ca2f37c73815358301ea22964 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Sun, 30 Nov 2025 17:09:49 +0300 Subject: [PATCH 07/15] fix: add support for token and cookie authorization of experts --- .../Controllers/ExpertsController.cs | 29 ++++++++++++++++++- .../Controllers/SolutionsController.cs | 2 +- .../HwProj.APIGateway.API/Startup.cs | 21 ++++++++++++-- .../Services/ExpertsService.cs | 22 ++++---------- .../src/components/Courses/CoursesList.tsx | 2 +- .../src/components/Experts/AuthLayout.tsx | 22 ++++++++++---- 6 files changed, 70 insertions(+), 28 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs index 597159b8e..2cfa3e7aa 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.IdentityModel.Tokens.Jwt; +using System.Net; using System.Threading.Tasks; using AutoMapper; using HwProj.AuthService.Client; @@ -9,6 +10,7 @@ using HwProj.Models.Result; using HwProj.Models.Roles; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace HwProj.APIGateway.API.Controllers; @@ -68,6 +70,31 @@ public async Task Register(RegisterExpertViewModel model) public async Task Login(TokenCredentials credentials) { var result = await AuthServiceClient.LoginExpert(credentials).ConfigureAwait(false); + + if (!result.Succeeded) + { + if (Request.Cookies.ContainsKey("accessToken")) + { + Response.Cookies.Delete("accessToken"); + } + + return BadRequest(result); + } + + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(credentials.AccessToken); + var expiresIn = jwt.ValidTo; + + + Response.Cookies.Append("accessToken", credentials.AccessToken, + new CookieOptions + { + Expires = expiresIn, + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict + }); + return Ok(result); } diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs index d3b6a4bf6..c2ac27a5b 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs @@ -324,7 +324,7 @@ await _coursesServiceClient.CreateCourseGroup(new CreateGroupViewModel(arrFullSt } [HttpPost("rateEmptySolution/{taskId}")] - [Authorize(Roles = Roles.LecturerOrExpertRole)] + [Authorize(Roles = Roles.LecturerOrExpertRole, AuthenticationSchemes = "JwtCookie, JwtBearer")] public async Task PostEmptySolutionWithRate(long taskId, SolutionViewModel solution) { var course = await _coursesServiceClient.GetCourseByTask(taskId); diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index baa6ba88d..f88a420f6 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -56,10 +56,10 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication(options => { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultAuthenticateScheme = "JwtCookie"; + options.DefaultChallengeScheme = "JwtCookie"; }) - .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x => + .AddJwtBearer("JwtCookie", x => { x.RequireHttpsMetadata = false; x.TokenValidationParameters = new TokenValidationParameters @@ -87,6 +87,21 @@ public void ConfigureServices(IServiceCollection services) return Task.CompletedTask; } }; + }) + .AddJwtBearer("JwtBearer", x => + { + x.RequireHttpsMetadata = false; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = "AuthService", + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + IssuerSigningKey = + new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings["SecurityKey"])), + ValidateIssuerSigningKey = true, + ClockSkew = TimeSpan.Zero + }; }); services.AddHttpClient(); diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/ExpertsService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/ExpertsService.cs index bc48b14b2..f0bced8c4 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/ExpertsService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/ExpertsService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AutoMapper; @@ -107,31 +108,20 @@ public async Task LoginExpertAsync(TokenCredentials tokenCredentials) { var tokenClaims = _tokenService.GetTokenClaims(tokenCredentials); - if (tokenClaims.Role != Roles.ExpertRole) - { - return Result.Failed("Невалидный токен: пользователь не является экспертом"); - } - if (tokenClaims.Email is null) { return Result.Failed("Невалидный токен: пользователь не найден"); } var expert = await _userManager.FindByEmailAsync(tokenClaims.Email); - if (expert.Id != tokenClaims.Id) + if (expert is null) { return Result.Failed("Невалидный токен: пользователь не найден"); } - var tokenCredentialsResult = await _tokenService.GetExpertTokenAsync(expert); - if (!tokenCredentialsResult.Succeeded) - { - return Result.Failed(tokenCredentialsResult.Errors); - } - - if (tokenCredentials.AccessToken != tokenCredentialsResult.Value.AccessToken) - { - return Result.Failed("Невалидный токен"); + var roles = await _userManager.GetRolesAsync(expert); + if (!roles.Contains(Roles.ExpertRole)) { + return Result.Failed("Невалидный токен: пользователь не является экспертом"); } await _signInManager.SignInAsync(expert, false).ConfigureAwait(false); diff --git a/hwproj.front/src/components/Courses/CoursesList.tsx b/hwproj.front/src/components/Courses/CoursesList.tsx index 4bf3917d5..c6ae7168d 100644 --- a/hwproj.front/src/components/Courses/CoursesList.tsx +++ b/hwproj.front/src/components/Courses/CoursesList.tsx @@ -37,7 +37,7 @@ export class CoursesList extends React.Component { > diff --git a/hwproj.front/src/components/Experts/AuthLayout.tsx b/hwproj.front/src/components/Experts/AuthLayout.tsx index ecb1665a2..64476dde1 100644 --- a/hwproj.front/src/components/Experts/AuthLayout.tsx +++ b/hwproj.front/src/components/Experts/AuthLayout.tsx @@ -20,10 +20,16 @@ const ExpertAuthLayout: FC = (props: IExpertAuthLayoutPr useEffect(() => { const checkToken = async () => { + if (!token) { + setIsTokenValid(false); + setIsLoading(false); + return; + } + const isExpired = ApiSingleton.authService.isTokenExpired(token); if (!isExpired) { - const isExpertLoggedIn = await ApiSingleton.expertsApi.expertsLogin(credentials) - if (isExpertLoggedIn.succeeded) { + try { + await ApiSingleton.expertsApi.expertsLogin(credentials); await ApiSingleton.authService.getUser(); setIsTokenValid(true); props.onLogin(); @@ -32,13 +38,17 @@ const ExpertAuthLayout: FC = (props: IExpertAuthLayoutPr if (isEdited.succeeded && isEdited.value) { setIsProfileAlreadyEdited(true); } - + } + catch { + setIsTokenValid(false); + } + finally { setIsLoading(false); - return } + } else { + setIsTokenValid(false); + setIsLoading(false); } - setIsTokenValid(false); - setIsLoading(false); }; checkToken(); From 5923724e5e49d9442972a84394c91e593f6451d8 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 4 Dec 2025 14:36:09 +0300 Subject: [PATCH 08/15] fix: small fix --- .../Controllers/ExpertsController.cs | 1 - .../Controllers/SolutionsController.cs | 2 +- .../HwProj.APIGateway.API/Startup.cs | 23 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs index 2cfa3e7aa..72488244d 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs @@ -85,7 +85,6 @@ public async Task Login(TokenCredentials credentials) var jwt = handler.ReadJwtToken(credentials.AccessToken); var expiresIn = jwt.ValidTo; - Response.Cookies.Append("accessToken", credentials.AccessToken, new CookieOptions { diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs index c2ac27a5b..d3b6a4bf6 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs @@ -324,7 +324,7 @@ await _coursesServiceClient.CreateCourseGroup(new CreateGroupViewModel(arrFullSt } [HttpPost("rateEmptySolution/{taskId}")] - [Authorize(Roles = Roles.LecturerOrExpertRole, AuthenticationSchemes = "JwtCookie, JwtBearer")] + [Authorize(Roles = Roles.LecturerOrExpertRole)] public async Task PostEmptySolutionWithRate(long taskId, SolutionViewModel solution) { var course = await _coursesServiceClient.GetCourseByTask(taskId); diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index f88a420f6..de645a7de 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -146,6 +146,29 @@ private static void ConfigureHwProjServiceSwaggerGen(IServiceCollection services var actionName = apiDesc.ActionDescriptor.RouteValues["action"]; return $"{controllerName}{actionName}"; }); + c.AddSecurityDefinition("Bearer", + new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter into field the word 'Bearer' following by space and JWT", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + c.AddSecurityRequirement( + new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = "Bearer", + Type = ReferenceType.SecurityScheme + } + }, + new List() + } + }); }); } } From 4630ff740a85c4c0b7aeb09404d351cab80968b1 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Thu, 4 Dec 2025 15:25:22 +0300 Subject: [PATCH 09/15] fix: fix with token auth for bots --- .../HwProj.APIGateway.API/Controllers/SolutionsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs index 1ab2fe869..6a6c2251e 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs @@ -327,7 +327,7 @@ await _coursesServiceClient.CreateCourseGroup(new CreateGroupViewModel(arrFullSt } [HttpPost("automated/{courseId}")] - [Authorize(Roles = Roles.LecturerOrExpertRole)] + [Authorize(Roles = Roles.LecturerOrExpertRole, AuthenticationSchemes = "JwtCookie, JwtBearer")] [ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)] public async Task PostAutomatedSolution(PostAutomatedSolutionModel model, long courseId) { From f2043c3614120ed3a5c0d334b6a26f2876fcea21 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Sat, 6 Dec 2025 14:19:17 +0300 Subject: [PATCH 10/15] fix: fix cors and add Unauthorized error handler --- .../Controllers/AccountController.cs | 1 + .../HwProj.APIGateway.API/Startup.cs | 2 +- hwproj.front/src/App.tsx | 14 ++++++++++---- hwproj.front/src/api/ApiSingleton.ts | 16 ++++++++++++++++ hwproj.front/src/services/AuthService.ts | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs index 8f8e636ad..01e476b65 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs @@ -155,6 +155,7 @@ public async Task RefreshToken() } [HttpPost("logout")] + [ProducesResponseType((int)HttpStatusCode.OK)] public IActionResult Logout() { ClearTokenCookie(); diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index de645a7de..e37b44805 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -126,7 +126,7 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env) app.UseRouting(); app.UseCors(x => x - .AllowAnyMethod() + .WithMethods("GET") .AllowAnyHeader() .SetIsOriginAllowed(_ => true) .AllowCredentials()); diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 1583ab806..5eaf67a24 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -16,7 +16,7 @@ import Register from "./components/Auth/Register"; import ExpertsNotebook from "./components/Experts/Notebook"; import StudentSolutionsPage from "./components/Solutions/StudentSolutionsPage"; import EditProfile from "./components/EditProfile"; -import ApiSingleton from "./api/ApiSingleton"; +import ApiSingleton, {setAunauthorizedHandler} from "./api/ApiSingleton"; import SystemInfoComponent from "./components/System/SystemInfoComponent"; import WrongPath from "./components/WrongPath"; import ResetPassword from "components/Auth/ResetPassword"; @@ -34,6 +34,7 @@ interface AppState { newNotificationsCount: number; appBarContextAction: AppBarContextAction; authReady: boolean; + needAuth: boolean; } const withRouter = (Component: any) => { @@ -58,12 +59,15 @@ class App extends Component<{ navigate: any }, AppState> { isExpert: ApiSingleton.authService.isExpert(), newNotificationsCount: 0, appBarContextAction: "Default", - authReady: false + authReady: false, + needAuth: false, }; appBarStateManager.setOnContextActionChange(appBarState => this.setState({appBarContextAction: appBarState})) } componentDidMount = async () => { + setUnauthorizedHandler(() => this.setState({needAuth: true})); + const user = await ApiSingleton.authService.getProfile(); this.setState( { @@ -90,7 +94,8 @@ class App extends Component<{ navigate: any }, AppState> { this.setState({ loggedIn: true, isLecturer: isLecturer, - isExpert: isExpert + isExpert: isExpert, + needAuth: false, }) if (!isExpert) { await this.updatedNewNotificationsCount() @@ -100,7 +105,7 @@ class App extends Component<{ navigate: any }, AppState> { logout = async () => { await ApiSingleton.authService.logout(); - this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0, authReady: true}); + this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0, authReady: true, needAuth: false}); this.props.navigate("/login"); } @@ -115,6 +120,7 @@ class App extends Component<{ navigate: any }, AppState> { onLogout={this.logout} contextAction={this.state.appBarContextAction}/> + }> }/> diff --git a/hwproj.front/src/api/ApiSingleton.ts b/hwproj.front/src/api/ApiSingleton.ts index 0f919e79e..989f615c2 100644 --- a/hwproj.front/src/api/ApiSingleton.ts +++ b/hwproj.front/src/api/ApiSingleton.ts @@ -69,12 +69,28 @@ function getApiBase(): string { return `${protocol}//${hostname}${effectivePort ? `:${effectivePort}` : ""}` } +let unauthorizedHandler: (() => void) | null = null + +export const setUnauthorizedHandler = (handler: (() => void) | null) => { + unauthorizedHandler = handler; +} + const cookieFetch = async (url: string, init: any) => { const response = await fetch(url, { ...init, credentials: "include" }); + const path = window.location.pathname; + + if (response.status === 401 && + !path.includes("login") && + !path.includes("register")){ + if (unauthorizedHandler) { + unauthorizedHandler() + } + } + return response; } diff --git a/hwproj.front/src/services/AuthService.ts b/hwproj.front/src/services/AuthService.ts index 7c0119217..b839dea83 100644 --- a/hwproj.front/src/services/AuthService.ts +++ b/hwproj.front/src/services/AuthService.ts @@ -1,4 +1,4 @@ -import {LoginViewModel, AccountApi, RegisterViewModel} from '../api'; +import {LoginViewModel, RegisterViewModel} from '../api'; import ApiSingleton from "../api/ApiSingleton"; import decode from "jwt-decode"; From 4021f309bacb126477836053fe411715b4fc67b2 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Sat, 6 Dec 2025 18:22:42 +0300 Subject: [PATCH 11/15] feat: add 401 error handler --- hwproj.front/src/App.tsx | 16 ++++++---------- hwproj.front/src/api/ApiSingleton.ts | 18 +++++++++++------- hwproj.front/src/services/AuthService.ts | 5 +++-- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 5eaf67a24..87fe3aba1 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -16,7 +16,7 @@ import Register from "./components/Auth/Register"; import ExpertsNotebook from "./components/Experts/Notebook"; import StudentSolutionsPage from "./components/Solutions/StudentSolutionsPage"; import EditProfile from "./components/EditProfile"; -import ApiSingleton, {setAunauthorizedHandler} from "./api/ApiSingleton"; +import ApiSingleton from "./api/ApiSingleton"; import SystemInfoComponent from "./components/System/SystemInfoComponent"; import WrongPath from "./components/WrongPath"; import ResetPassword from "components/Auth/ResetPassword"; @@ -24,6 +24,8 @@ import PasswordRecovery from "components/Auth/PasswordRecovery"; import AuthLayout from "./AuthLayout"; import ExpertAuthLayout from "./components/Experts/AuthLayout"; import TrackPageChanges from "TrackPageChanges"; +import {Alert, Button} from "@mui/material"; +import {Snackbar} from "@material-ui/core"; // TODO: add flux @@ -34,7 +36,6 @@ interface AppState { newNotificationsCount: number; appBarContextAction: AppBarContextAction; authReady: boolean; - needAuth: boolean; } const withRouter = (Component: any) => { @@ -59,15 +60,12 @@ class App extends Component<{ navigate: any }, AppState> { isExpert: ApiSingleton.authService.isExpert(), newNotificationsCount: 0, appBarContextAction: "Default", - authReady: false, - needAuth: false, + authReady: false }; appBarStateManager.setOnContextActionChange(appBarState => this.setState({appBarContextAction: appBarState})) } componentDidMount = async () => { - setUnauthorizedHandler(() => this.setState({needAuth: true})); - const user = await ApiSingleton.authService.getProfile(); this.setState( { @@ -94,8 +92,7 @@ class App extends Component<{ navigate: any }, AppState> { this.setState({ loggedIn: true, isLecturer: isLecturer, - isExpert: isExpert, - needAuth: false, + isExpert: isExpert }) if (!isExpert) { await this.updatedNewNotificationsCount() @@ -105,7 +102,7 @@ class App extends Component<{ navigate: any }, AppState> { logout = async () => { await ApiSingleton.authService.logout(); - this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0, authReady: true, needAuth: false}); + this.setState({loggedIn: false, isLecturer: false, isExpert: false, newNotificationsCount: 0, authReady: true}); this.props.navigate("/login"); } @@ -120,7 +117,6 @@ class App extends Component<{ navigate: any }, AppState> { onLogout={this.logout} contextAction={this.state.appBarContextAction}/> - }> }/> diff --git a/hwproj.front/src/api/ApiSingleton.ts b/hwproj.front/src/api/ApiSingleton.ts index 989f615c2..536631b1b 100644 --- a/hwproj.front/src/api/ApiSingleton.ts +++ b/hwproj.front/src/api/ApiSingleton.ts @@ -69,10 +69,15 @@ function getApiBase(): string { return `${protocol}//${hostname}${effectivePort ? `:${effectivePort}` : ""}` } -let unauthorizedHandler: (() => void) | null = null +let skipRedirect = false; -export const setUnauthorizedHandler = (handler: (() => void) | null) => { - unauthorizedHandler = handler; +export async function runWithoutAuthRedirect(functionToRun: () => Promise) { + skipRedirect = true; + try { + return await functionToRun(); + } finally { + skipRedirect = false; + } } const cookieFetch = async (url: string, init: any) => { @@ -85,10 +90,9 @@ const cookieFetch = async (url: string, init: any) => { if (response.status === 401 && !path.includes("login") && - !path.includes("register")){ - if (unauthorizedHandler) { - unauthorizedHandler() - } + !path.includes("register") && + !skipRedirect){ + window.location.href = `/login?returnUrl=${window.location.pathname}`; } return response; diff --git a/hwproj.front/src/services/AuthService.ts b/hwproj.front/src/services/AuthService.ts index b839dea83..d88086a70 100644 --- a/hwproj.front/src/services/AuthService.ts +++ b/hwproj.front/src/services/AuthService.ts @@ -1,5 +1,5 @@ import {LoginViewModel, RegisterViewModel} from '../api'; -import ApiSingleton from "../api/ApiSingleton"; +import ApiSingleton, {runWithoutAuthRedirect} from "../api/ApiSingleton"; import decode from "jwt-decode"; @@ -29,7 +29,8 @@ export default class AuthService { public async getUser() { try { - const accountData = await ApiSingleton.accountApi.accountGetUserSummary(); + const accountData = await runWithoutAuthRedirect(() => + ApiSingleton.accountApi.accountGetUserSummary()); if (accountData) { this._user = { id: accountData.userId!, From c458309ad902d7e5fd12817b78094aa665c24a39 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Sun, 7 Dec 2025 20:06:04 +0300 Subject: [PATCH 12/15] fix: small fix --- HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs index de645a7de..d434ce40b 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs @@ -12,8 +12,6 @@ using IStudentsInfo; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.CookiePolicy; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; From fad511e361e5dc3906ad8a5746f4d4ef9740c0d0 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Sun, 7 Dec 2025 21:35:29 +0300 Subject: [PATCH 13/15] fix: fix handler --- hwproj.front/src/App.tsx | 2 -- hwproj.front/src/api/ApiSingleton.ts | 25 ++++++++++++------------ hwproj.front/src/services/AuthService.ts | 5 ++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/hwproj.front/src/App.tsx b/hwproj.front/src/App.tsx index 87fe3aba1..1583ab806 100644 --- a/hwproj.front/src/App.tsx +++ b/hwproj.front/src/App.tsx @@ -24,8 +24,6 @@ import PasswordRecovery from "components/Auth/PasswordRecovery"; import AuthLayout from "./AuthLayout"; import ExpertAuthLayout from "./components/Experts/AuthLayout"; import TrackPageChanges from "TrackPageChanges"; -import {Alert, Button} from "@mui/material"; -import {Snackbar} from "@material-ui/core"; // TODO: add flux diff --git a/hwproj.front/src/api/ApiSingleton.ts b/hwproj.front/src/api/ApiSingleton.ts index 536631b1b..889f08b6c 100644 --- a/hwproj.front/src/api/ApiSingleton.ts +++ b/hwproj.front/src/api/ApiSingleton.ts @@ -69,17 +69,6 @@ function getApiBase(): string { return `${protocol}//${hostname}${effectivePort ? `:${effectivePort}` : ""}` } -let skipRedirect = false; - -export async function runWithoutAuthRedirect(functionToRun: () => Promise) { - skipRedirect = true; - try { - return await functionToRun(); - } finally { - skipRedirect = false; - } -} - const cookieFetch = async (url: string, init: any) => { const response = await fetch(url, { ...init, @@ -90,20 +79,30 @@ const cookieFetch = async (url: string, init: any) => { if (response.status === 401 && !path.includes("login") && - !path.includes("register") && - !skipRedirect){ + !path.includes("register")){ window.location.href = `/login?returnUrl=${window.location.pathname}`; } return response; } +const cookieFetchWithoutRedirect = async (url: string, init: any) => { + const response = await fetch(url, { + ...init, + credentials: "include" + }); + + return response; +} + const basePath = getApiBase() const authService = new AuthService() const apiConfig = { basePath: basePath, } +export const accountApiWithoutRedirect = new AccountApi(apiConfig, undefined, cookieFetchWithoutRedirect); + let ApiSingleton: Api; ApiSingleton = new Api( new AccountApi(apiConfig, undefined, cookieFetch), diff --git a/hwproj.front/src/services/AuthService.ts b/hwproj.front/src/services/AuthService.ts index d88086a70..614f5ad6c 100644 --- a/hwproj.front/src/services/AuthService.ts +++ b/hwproj.front/src/services/AuthService.ts @@ -1,5 +1,5 @@ import {LoginViewModel, RegisterViewModel} from '../api'; -import ApiSingleton, {runWithoutAuthRedirect} from "../api/ApiSingleton"; +import ApiSingleton, {accountApiWithoutRedirect} from "../api/ApiSingleton"; import decode from "jwt-decode"; @@ -29,8 +29,7 @@ export default class AuthService { public async getUser() { try { - const accountData = await runWithoutAuthRedirect(() => - ApiSingleton.accountApi.accountGetUserSummary()); + const accountData = await accountApiWithoutRedirect.accountGetUserSummary(); if (accountData) { this._user = { id: accountData.userId!, From 3ace4850a52d097eb01f60e78e7fbc84a859fcdd Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Tue, 16 Dec 2025 04:07:43 +0300 Subject: [PATCH 14/15] test: add tests --- .../Controllers/AccountController.cs | 4 +- .../Controllers/ExpertsController.cs | 4 +- .../APIGatewayAuthControllersTests.cs | 347 ++++++++++++++++++ .../HwProj.APIGateway.Tests.csproj | 26 +- .../Properties/launchSettings.json | 30 -- .../HwProj.APIGateway.Tests/UnitTest1.cs | 0 .../Services/AccountService.cs | 1 - .../AuthServiceTests.cs | 28 +- .../ViewModels/EditAccountViewModel.cs | 10 +- .../ViewModels/RegisterExpertViewModel.cs | 8 +- .../EventHandlers/RegisterEventHandler.cs | 38 +- HwProj.sln | 7 + 12 files changed, 435 insertions(+), 68 deletions(-) create mode 100644 HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs delete mode 100644 HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json delete mode 100644 HwProj.APIGateway/HwProj.APIGateway.Tests/UnitTest1.cs diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs index 01e476b65..e6bc126d7 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/AccountController.cs @@ -123,7 +123,7 @@ public async Task Login(LoginViewModel model) { Expires = tokenMeta.Value.ExpiresIn, HttpOnly = true, - Secure = true, + Secure = false, SameSite = SameSiteMode.Strict }); @@ -147,7 +147,7 @@ public async Task RefreshToken() { Expires = tokenMeta.Value.ExpiresIn, HttpOnly = true, - Secure = true, + Secure = false, SameSite = SameSiteMode.Strict }); diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs index 72488244d..d14efc6d2 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/ExpertsController.cs @@ -78,7 +78,7 @@ public async Task Login(TokenCredentials credentials) Response.Cookies.Delete("accessToken"); } - return BadRequest(result); + return Unauthorized(result); } var handler = new JwtSecurityTokenHandler(); @@ -90,7 +90,7 @@ public async Task Login(TokenCredentials credentials) { Expires = expiresIn, HttpOnly = true, - Secure = true, + Secure = false, SameSite = SameSiteMode.Strict }); diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs b/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs new file mode 100644 index 000000000..22ae17f0a --- /dev/null +++ b/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs @@ -0,0 +1,347 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Net; +using System.Net.Mail; +using System.Security.Claims; +using System.Text; +using AutoFixture; +using FluentAssertions; +using HwProj.Models.AuthService.DTO; +using HwProj.Models.AuthService.ViewModels; +using HwProj.Models.Result; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; + +namespace AuthTests +{ + public class Tests + { + private HttpClient _noCookieClient; + private HttpClient _cookieClient; + private HttpClient _authServiceClient; + private CookieContainer _cookieContainer; + private const string _devPassword = "devPassword"; + + [SetUp] + public void Setup() + { + const string address = "http://localhost:5000"; + const string authServiceAddress = "http://localhost:5001"; + + var handlerNoCookie = new HttpClientHandler() + { + UseCookies = false + }; + + _noCookieClient = new HttpClient(handlerNoCookie) + { + BaseAddress = new Uri(address) + }; + + _cookieContainer = new CookieContainer(); + var handlerWithCookie = new HttpClientHandler() + { + UseCookies = true, + CookieContainer = _cookieContainer + + }; + + _cookieClient = new HttpClient(handlerWithCookie) + { + BaseAddress = new Uri(address) + }; + + _authServiceClient = new HttpClient() + { + BaseAddress = new Uri(authServiceAddress) + }; + } + + private Claim[] ValidateToken(string token) + { + const string secret = "U8_.wpvk93fPWG() + .With(x => x.Email, new Fixture().Create().Address); + + return fixture.Create(); + } + + private static LoginViewModel GenerateLoginViewModel(RegisterViewModel model) + => new LoginViewModel + { + Email = model.Email, + Password = _devPassword, + RememberMe = false + }; + + private static LoginViewModel GenerateWrongLoginViewModel(RegisterViewModel model) + => new LoginViewModel + { + Email = model.Email, + Password = "wrongPassword", + RememberMe = false + }; + + private static async Task PostJsonAsync(HttpClient client, string url, object data) + { + var json = JsonConvert.SerializeObject(data); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + return await client.PostAsync(url, content); + } + + private static async Task GetJsonAsync(HttpClient client, string url) + => await client.GetAsync(url); + + private static async Task PutJsonAsync(HttpClient client, string url, object data) + { + var json = JsonConvert.SerializeObject(data); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + return await client.PutAsync(url, content); + } + + private static async Task ReadJsonAsync(HttpResponseMessage response) + { + var json = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(json); + } + + [Test] + public async Task LoginCorrectDataShouldSetCookie() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + loginResponse.Headers.Contains("Set-Cookie").Should().BeTrue(); + var cookie = loginResponse.Headers.GetValues("Set-Cookie").First(); + cookie.Should().Contain("httponly"); + } + + [Test] + public async Task LoginIncorrectDataShouldNotSetCookie() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + registerResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var loginData = GenerateWrongLoginViewModel(user); + var loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); + + await Task.Delay(1000); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + loginResponse.Headers.Contains("Set-Cookie").Should().BeFalse(); + } + + [Test] + public async Task GetUserSummaryShouldReturnsUser() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var summaryResponse = await GetJsonAsync(_cookieClient, "/api/Account/getUserSummary"); + summaryResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var summary = await ReadJsonAsync(summaryResponse); + summary.Email.Should().Be(user.Email); + } + + [Test] + public async Task GetUserSummaryWithoutCookieShouldReturnsUnauthorized() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_noCookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + + var response = await GetJsonAsync(_noCookieClient, "/api/Account/getUserSummary"); + response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } + + [Test] + public async Task LogoutShouldRemoveCookie() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var logoutResponse = await PostJsonAsync(_cookieClient, "/api/Account/logout", null); + logoutResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var cookie = _cookieContainer.GetCookies(new Uri("http://localhost:5000")); + var accessToken = cookie["accessToken"]?.Value; + accessToken.Should().BeNull(); + + var response = await GetJsonAsync(_cookieClient, "/api/Account/getUserSummary"); + response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } + + [Test] + public async Task RefreshTokenShouldSetValidToken() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var newEmail = "new" + user.Email; + + var editModel = new EditAccountViewModel + { + Name = user.Name, + Surname = user.Surname, + Email = newEmail, + }; + + var editResponse = await PutJsonAsync(_cookieClient, "/api/Account/edit", editModel); + editResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var beforeCookie = _cookieContainer.GetCookies(new Uri("http://localhost:5000")); + var accessTokenBefore = beforeCookie["accessToken"]?.Value; + + var refreshResponse = await GetJsonAsync(_cookieClient, "/api/Account/refreshToken"); + refreshResponse.StatusCode.Should().Be(HttpStatusCode.OK); + refreshResponse.Headers.Contains("Set-Cookie").Should().BeTrue(); + + var afterCookie = _cookieContainer.GetCookies(new Uri("http://localhost:5000")); + var accessTokenAfter = afterCookie["accessToken"]?.Value; + + var tokenClaims = ValidateToken(accessTokenAfter); + + accessTokenBefore.Should().NotBe(accessTokenAfter); + tokenClaims[2].Value.Should().Be(newEmail); + } + + [Test] + public async Task ExpertLoginShouldReturnExpectedResult() + { + var loginData = new LoginViewModel + { + Email = "admin@gmail.com", + Password = "Admin@1234" + }; + var loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); + for (int i = 0; i < 10; i++) + { + if (loginResponse.StatusCode == HttpStatusCode.OK) + { + break; + } + loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); + + await Task.Delay(500); + } + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var model = new RegisterExpertViewModel + { + Name = "Expert", + Surname = "Expert", + Email = "expert@mail.ru" + }; + + var registerExpertResponse = await PostJsonAsync(_cookieClient, "/api/Experts/register", model); + registerExpertResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var getTokenResponse = await GetJsonAsync(_cookieClient, "/api/Experts/getToken?expertEmail=expert@mail.ru"); + getTokenResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var tokenMeta = await ReadJsonAsync>(getTokenResponse); + tokenMeta.Succeeded.Should().BeTrue(); + + var logoutResponse = await PostJsonAsync(_cookieClient, "/api/Account/logout", null); + logoutResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var expertLoginResponse = await PostJsonAsync(_cookieClient, "/api/Experts/login", tokenMeta.Value); + expertLoginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var cookie = loginResponse.Headers.GetValues("Set-Cookie").First(); + cookie.Should().Contain("httponly"); + + var protectedResponse = await PostJsonAsync(_cookieClient, "/api/Experts/SetProfileIsEdited", null); + protectedResponse.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Test] + public async Task AuthorizedEnpointShouldRequireRole() + { + var user = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + + var loginData = GenerateLoginViewModel(user); + + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); + + loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var lecturerModel = new InviteLecturerViewModel + { + Email = "lecturer@mail.ru" + }; + + var summaryResponse = await GetJsonAsync(_cookieClient, "/api/Account/getUserSummary"); + summaryResponse.StatusCode.Should().Be(HttpStatusCode.OK); + + var response = await PostJsonAsync(_cookieClient, "/api/Account/inviteNewLecturer", lecturerModel); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + private static async Task RetryUntilSuccess(Func> action) + { + var response = await action(); + for (int i = 0; i < 10; i++) + { + if (response.StatusCode == HttpStatusCode.OK) + { + break; + } + await Task.Delay(500); + response = await action(); + } + + return response; + } + + [TearDown] + public void Dispose() + { + _noCookieClient.Dispose(); + _cookieClient.Dispose(); + } + } +} \ No newline at end of file diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/HwProj.APIGateway.Tests.csproj b/HwProj.APIGateway/HwProj.APIGateway.Tests/HwProj.APIGateway.Tests.csproj index 89dd6ef3f..51e8105f0 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.Tests/HwProj.APIGateway.Tests.csproj +++ b/HwProj.APIGateway/HwProj.APIGateway.Tests/HwProj.APIGateway.Tests.csproj @@ -1,24 +1,30 @@  - netcoreapp2.2 - + net8.0 + latest + enable + enable false - - - + + + + + + + + + - - Never - + - - + + diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json b/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json deleted file mode 100644 index 3643da0e8..000000000 --- a/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59818", - "sslPort": 44338 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "HwProj.AuthService.API": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/UnitTest1.cs b/HwProj.APIGateway/HwProj.APIGateway.Tests/UnitTest1.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs index 76554b770..20f8c4e28 100644 --- a/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs +++ b/HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs @@ -13,7 +13,6 @@ using HwProj.Models.AuthService.ViewModels; using HwProj.Models.Result; using HwProj.NotificationService.Events.AuthService; -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Octokit; diff --git a/HwProj.AuthService/HwProj.AuthService.IntegrationTests/AuthServiceTests.cs b/HwProj.AuthService/HwProj.AuthService.IntegrationTests/AuthServiceTests.cs index 404e38e43..4d5178c02 100644 --- a/HwProj.AuthService/HwProj.AuthService.IntegrationTests/AuthServiceTests.cs +++ b/HwProj.AuthService/HwProj.AuthService.IntegrationTests/AuthServiceTests.cs @@ -24,10 +24,13 @@ namespace HwProj.AuthService.IntegrationTests { public class Tests { + private const string _testUserEmail = "admin@gmail.com"; + private const string _testUserPassword = "Admin@1234"; + private Claim[] ValidateToken(Result resultData) { - const string secret = "this is a string used for encrypt and decrypt token"; - var key = Encoding.ASCII.GetBytes(secret); + const string secret = "U8_.wpvk93fPWG resultData) ValidateIssuer = true, ValidateAudience = false, ValidateLifetime = true, - IssuerSigningKey = null, + IssuerSigningKey = key, ValidateIssuerSigningKey = true }; var claims = handler.ValidateToken(resultData.Value.AccessToken, validations, out var tokenSecure); @@ -60,12 +63,8 @@ private AuthServiceClient CreateAuthServiceClient() private static RegisterViewModel GenerateRegisterViewModel() { - var password = new Fixture().Create(); - var fixture = new Fixture().Build() .With(x => x.Email, new Fixture().Create().Address); - // .With(x => x.Password, password) - // .With(x => x.PasswordConfirm, password); return fixture.Create(); } @@ -74,7 +73,6 @@ private static LoginViewModel GenerateLoginViewModel(RegisterViewModel model) => new LoginViewModel { Email = model.Email, - // Password = model.Password, RememberMe = false }; @@ -187,7 +185,13 @@ public async Task TestLoginUser() var userData = GenerateRegisterViewModel(); await _authServiceClient.Register(userData); - var loginData = GenerateLoginViewModel(userData); + var loginData = new LoginViewModel + { + Email = _testUserEmail, + Password = _testUserPassword, + RememberMe = false, + }; + var resultData = await _authServiceClient.Login(loginData); resultData.Succeeded.Should().BeTrue(); @@ -195,11 +199,11 @@ public async Task TestLoginUser() resultData.Value.AccessToken.Should().NotBeNullOrEmpty(); var claims = ValidateToken(resultData); - var userId = await _authServiceClient.FindByEmailAsync(userData.Email); + var userId = await _authServiceClient.FindByEmailAsync(_testUserEmail); claims[1].Value.Should().Be(userId); - claims[2].Value.Should().Be(userData.Email); - claims[3].Value.Should().Be(Roles.StudentRole); + claims[2].Value.Should().Be(_testUserEmail); + claims[3].Value.Should().Be(Roles.LecturerRole); } [Test] diff --git a/HwProj.Common/HwProj.Models/AuthService/ViewModels/EditAccountViewModel.cs b/HwProj.Common/HwProj.Models/AuthService/ViewModels/EditAccountViewModel.cs index 1198655af..930a63e7a 100644 --- a/HwProj.Common/HwProj.Models/AuthService/ViewModels/EditAccountViewModel.cs +++ b/HwProj.Common/HwProj.Models/AuthService/ViewModels/EditAccountViewModel.cs @@ -8,13 +8,13 @@ public class EditAccountViewModel [Required] public string Surname { get; set; } - public string MiddleName { get; set; } + public string? MiddleName { get; set; } [DataType(DataType.EmailAddress)] - public string Email { get; set; } + public string? Email { get; set; } - public string Bio { get; set; } - - public string CompanyName { get; set; } + public string? Bio { get; set; } + + public string? CompanyName { get; set; } } } \ No newline at end of file diff --git a/HwProj.Common/HwProj.Models/AuthService/ViewModels/RegisterExpertViewModel.cs b/HwProj.Common/HwProj.Models/AuthService/ViewModels/RegisterExpertViewModel.cs index 7ff7a0909..4614dd7ff 100644 --- a/HwProj.Common/HwProj.Models/AuthService/ViewModels/RegisterExpertViewModel.cs +++ b/HwProj.Common/HwProj.Models/AuthService/ViewModels/RegisterExpertViewModel.cs @@ -11,15 +11,15 @@ public class RegisterExpertViewModel [Required] public string Surname { get; set; } - public string MiddleName { get; set; } + public string? MiddleName { get; set; } [Required] [DataType(DataType.EmailAddress)] public string Email { get; set; } - public string Bio { get; set; } - - public string CompanyName { get; set; } + public string? Bio { get; set; } + + public string? CompanyName { get; set; } public List Tags { get; set; } = new List(); } diff --git a/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs b/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs index 322156d59..f0b9910d6 100644 --- a/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs +++ b/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs @@ -1,7 +1,9 @@ using System; using System.Threading.Tasks; using System.Web; +using HwProj.AuthService.Client; using HwProj.EventBus.Client.Interfaces; +using HwProj.Models.AuthService.ViewModels; using HwProj.NotificationService.Events.AuthService; using HwProj.NotificationsService.API.Models; using HwProj.NotificationsService.API.Repositories; @@ -17,17 +19,21 @@ public class RegisterEventHandler : EventHandlerBase private readonly IEmailService _emailService; private readonly IConfiguration _configuration; private readonly bool _isDevelopmentEnv; + private readonly IAuthServiceClient _authServiceClient; + private const string _devPassword = "devPassword"; public RegisterEventHandler( INotificationsRepository notificationRepository, IEmailService emailService, IConfiguration configuration, - IHostingEnvironment env) + IHostingEnvironment env, + IAuthServiceClient authServiceClient) { _notificationRepository = notificationRepository; _emailService = emailService; _configuration = configuration; _isDevelopmentEnv = env.IsDevelopment(); + _authServiceClient = authServiceClient; } public override async Task HandleAsync(StudentRegisterEvent @event) @@ -36,6 +42,13 @@ public override async Task HandleAsync(StudentRegisterEvent @event) var recoveryLink = $"{frontendUrl}/resetPassword?token={HttpUtility.UrlEncode(@event.ChangePasswordToken)}&id={HttpUtility.UrlEncode(@event.UserId)}"; + if (_isDevelopmentEnv) + { + await HandleDevAsync(@event); + Console.WriteLine(recoveryLink); + return; + } + var notification = new Notification { Sender = "AuthService", @@ -48,11 +61,32 @@ public override async Task HandleAsync(StudentRegisterEvent @event) Owner = @event.UserId }; - if (_isDevelopmentEnv) Console.WriteLine(recoveryLink); var addNotificationTask = _notificationRepository.AddAsync(notification); var sendEmailTask = _emailService.SendEmailAsync(notification, @event.Email, "HwProj"); await Task.WhenAll(addNotificationTask, sendEmailTask); } + + private async Task HandleDevAsync(StudentRegisterEvent @event) + { + var resetModel = new ResetPasswordViewModel + { + UserId = @event.UserId, + Token = @event.ChangePasswordToken, + Password = _devPassword, + PasswordConfirm = _devPassword + }; + + var result = await _authServiceClient.ResetPassword(resetModel); + + if (result.Succeeded) + { + Console.WriteLine("Password changed"); + } + else + { + Console.WriteLine("Password not changed"); + } + } } } diff --git a/HwProj.sln b/HwProj.sln index 6b9064aec..7769bfcb9 100644 --- a/HwProj.sln +++ b/HwProj.sln @@ -82,6 +82,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HwProj.NotificationService. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HwProj.Repositories.Net8", "HwProj.Common\HwProj.Repositories.Net8\HwProj.Repositories.Net8.csproj", "{9E3A99DA-6975-46A7-90DB-69B708C8FF2C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HwProj.APIGateway.Tests", "HwProj.APIGateway\HwProj.APIGateway.Tests\HwProj.APIGateway.Tests.csproj", "{58C724C5-D05C-4F95-ACD1-1AF4190D49A4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -200,6 +202,10 @@ Global {9E3A99DA-6975-46A7-90DB-69B708C8FF2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E3A99DA-6975-46A7-90DB-69B708C8FF2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E3A99DA-6975-46A7-90DB-69B708C8FF2C}.Release|Any CPU.Build.0 = Release|Any CPU + {58C724C5-D05C-4F95-ACD1-1AF4190D49A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58C724C5-D05C-4F95-ACD1-1AF4190D49A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58C724C5-D05C-4F95-ACD1-1AF4190D49A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58C724C5-D05C-4F95-ACD1-1AF4190D49A4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -233,6 +239,7 @@ Global {6D9F7095-5E38-4FC6-9913-C4015B233656} = {77D857A8-45C6-4432-B4BF-A2F2C9ECA7FE} {C04A9D3D-B299-4534-B7B1-36DF92B1BC87} = {1EAEB779-E7C8-4EF9-B9A9-22CB8E3C246D} {9E3A99DA-6975-46A7-90DB-69B708C8FF2C} = {77D857A8-45C6-4432-B4BF-A2F2C9ECA7FE} + {58C724C5-D05C-4F95-ACD1-1AF4190D49A4} = {DC0D1EE7-D2F8-4D15-8CC6-69A0A0A938D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C03BF138-4A5B-4261-9495-6D3AC6CE9779} From 87ee507cceca30976da8f58609d8ed5809de18c1 Mon Sep 17 00:00:00 2001 From: alexmagikov Date: Tue, 16 Dec 2025 23:44:32 +0300 Subject: [PATCH 15/15] fix: fix tests --- .../APIGatewayAuthControllersTests.cs | 117 ++++++++++-------- .../Properties/launchSettings.json | 30 +++++ .../EventHandlers/RegisterEventHandler.cs | 25 ++-- 3 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs b/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs index 22ae17f0a..617d01d4f 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.Tests/APIGatewayAuthControllersTests.cs @@ -11,7 +11,7 @@ using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -namespace AuthTests +namespace HwProj.APIGateway.Tests { public class Tests { @@ -122,11 +122,42 @@ private static async Task ReadJsonAsync(HttpResponseMessage response) return JsonConvert.DeserializeObject(json); } + private static async Task RetryUntilSuccess(Func> action) + { + var response = await action(); + for (int i = 0; i < 10; i++) + { + if (response.StatusCode == HttpStatusCode.OK) + { + break; + } + await Task.Delay(500); + response = await action(); + } + + return response; + } + + private static async Task ReadOkAsync(HttpResponseMessage response) + { + response.StatusCode.Should().Be(HttpStatusCode.OK); + var resultResponse = await ReadJsonAsync(response); + resultResponse.Succeeded.Should().BeTrue(); + } + + private static async Task ReadOkAsync(HttpResponseMessage response) + { + response.StatusCode.Should().Be(HttpStatusCode.OK); + var resultResponse = await ReadJsonAsync>(response); + resultResponse.Succeeded.Should().BeTrue(); + } + [Test] public async Task LoginCorrectDataShouldSetCookie() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); @@ -142,7 +173,7 @@ public async Task LoginIncorrectDataShouldNotSetCookie() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); - registerResponse.StatusCode.Should().Be(HttpStatusCode.OK); + await ReadOkAsync(registerResponse); var loginData = GenerateWrongLoginViewModel(user); var loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); @@ -158,6 +189,7 @@ public async Task GetUserSummaryShouldReturnsUser() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); @@ -165,7 +197,6 @@ public async Task GetUserSummaryShouldReturnsUser() loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); var summaryResponse = await GetJsonAsync(_cookieClient, "/api/Account/getUserSummary"); - summaryResponse.StatusCode.Should().Be(HttpStatusCode.OK); var summary = await ReadJsonAsync(summaryResponse); summary.Email.Should().Be(user.Email); } @@ -175,13 +206,12 @@ public async Task GetUserSummaryWithoutCookieShouldReturnsUnauthorized() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_noCookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); - loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var response = await GetJsonAsync(_noCookieClient, "/api/Account/getUserSummary"); response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } @@ -191,14 +221,13 @@ public async Task LogoutShouldRemoveCookie() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); - loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); var logoutResponse = await PostJsonAsync(_cookieClient, "/api/Account/logout", null); - logoutResponse.StatusCode.Should().Be(HttpStatusCode.OK); var cookie = _cookieContainer.GetCookies(new Uri("http://localhost:5000")); var accessToken = cookie["accessToken"]?.Value; @@ -213,14 +242,13 @@ public async Task RefreshTokenShouldSetValidToken() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); - loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); var newEmail = "new" + user.Email; - var editModel = new EditAccountViewModel { Name = user.Name, @@ -229,7 +257,7 @@ public async Task RefreshTokenShouldSetValidToken() }; var editResponse = await PutJsonAsync(_cookieClient, "/api/Account/edit", editModel); - editResponse.StatusCode.Should().Be(HttpStatusCode.OK); + await ReadOkAsync(editResponse); var beforeCookie = _cookieContainer.GetCookies(new Uri("http://localhost:5000")); var accessTokenBefore = beforeCookie["accessToken"]?.Value; @@ -242,7 +270,6 @@ public async Task RefreshTokenShouldSetValidToken() var accessTokenAfter = afterCookie["accessToken"]?.Value; var tokenClaims = ValidateToken(accessTokenAfter); - accessTokenBefore.Should().NotBe(accessTokenAfter); tokenClaims[2].Value.Should().Be(newEmail); } @@ -250,63 +277,59 @@ public async Task RefreshTokenShouldSetValidToken() [Test] public async Task ExpertLoginShouldReturnExpectedResult() { - var loginData = new LoginViewModel + var lecturerData = GenerateRegisterViewModel(); + var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", lecturerData); + await ReadOkAsync(registerResponse); + + var inviteLecturerModel = new InviteLecturerViewModel { - Email = "admin@gmail.com", - Password = "Admin@1234" + Email = lecturerData.Email }; - var loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); - for (int i = 0; i < 10; i++) - { - if (loginResponse.StatusCode == HttpStatusCode.OK) - { - break; - } - loginResponse = await PostJsonAsync(_cookieClient, "/api/Account/login", loginData); - await Task.Delay(500); - } + var lecturerInviteResponse = await PostJsonAsync(_authServiceClient, "api/account/inviteNewLecturer", inviteLecturerModel); + await ReadOkAsync(lecturerInviteResponse); + var lecturerLoginData = GenerateLoginViewModel(lecturerData); + var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", lecturerLoginData)); loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var email = "expertFrom" + lecturerData.Email; var model = new RegisterExpertViewModel { - Name = "Expert", - Surname = "Expert", - Email = "expert@mail.ru" + Name = "expertFrom" + lecturerData.Name, + Surname = "expertFrom" + lecturerData.Surname, + Email = email }; - var registerExpertResponse = await PostJsonAsync(_cookieClient, "/api/Experts/register", model); - registerExpertResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var registerExpertResponse = await PostJsonAsync(_cookieClient, $"api/Experts/register", model); + await ReadOkAsync(registerExpertResponse); - var getTokenResponse = await GetJsonAsync(_cookieClient, "/api/Experts/getToken?expertEmail=expert@mail.ru"); - getTokenResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var getTokenResponse = await GetJsonAsync(_cookieClient, $"/api/Experts/getToken?expertEmail={email}"); var tokenMeta = await ReadJsonAsync>(getTokenResponse); tokenMeta.Succeeded.Should().BeTrue(); var logoutResponse = await PostJsonAsync(_cookieClient, "/api/Account/logout", null); - logoutResponse.StatusCode.Should().Be(HttpStatusCode.OK); var expertLoginResponse = await PostJsonAsync(_cookieClient, "/api/Experts/login", tokenMeta.Value); expertLoginResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var cookie = loginResponse.Headers.GetValues("Set-Cookie").First(); + var cookie = expertLoginResponse.Headers.GetValues("Set-Cookie").First(); + expertLoginResponse.Headers.Contains("Set-Cookie").Should().BeTrue(); cookie.Should().Contain("httponly"); var protectedResponse = await PostJsonAsync(_cookieClient, "/api/Experts/SetProfileIsEdited", null); - protectedResponse.StatusCode.Should().Be(HttpStatusCode.OK); + await ReadOkAsync(protectedResponse); } [Test] - public async Task AuthorizedEnpointShouldRequireRole() + public async Task AuthorizedEndpointShouldRequireRole() { var user = GenerateRegisterViewModel(); var registerResponse = await PostJsonAsync(_cookieClient, "/api/Account/register", user); + await ReadOkAsync(registerResponse); var loginData = GenerateLoginViewModel(user); - var loginResponse = await RetryUntilSuccess(() => PostJsonAsync(_cookieClient, "/api/Account/login", loginData)); - loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); var lecturerModel = new InviteLecturerViewModel @@ -315,33 +338,19 @@ public async Task AuthorizedEnpointShouldRequireRole() }; var summaryResponse = await GetJsonAsync(_cookieClient, "/api/Account/getUserSummary"); - summaryResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var summary = await ReadJsonAsync(summaryResponse); + summary.Email.Should().Be(user.Email); var response = await PostJsonAsync(_cookieClient, "/api/Account/inviteNewLecturer", lecturerModel); response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } - private static async Task RetryUntilSuccess(Func> action) - { - var response = await action(); - for (int i = 0; i < 10; i++) - { - if (response.StatusCode == HttpStatusCode.OK) - { - break; - } - await Task.Delay(500); - response = await action(); - } - - return response; - } - [TearDown] public void Dispose() { _noCookieClient.Dispose(); _cookieClient.Dispose(); + _authServiceClient.Dispose(); } } } \ No newline at end of file diff --git a/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json b/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..3643da0e8 --- /dev/null +++ b/HwProj.APIGateway/HwProj.APIGateway.Tests/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59818", + "sslPort": 44338 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "HwProj.AuthService.API": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs b/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs index f0b9910d6..f2888e03e 100644 --- a/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs +++ b/HwProj.NotificationsService/HwProj.NotificationsService.API/EventHandlers/RegisterEventHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using System.Web; using HwProj.AuthService.Client; @@ -77,15 +78,25 @@ private async Task HandleDevAsync(StudentRegisterEvent @event) PasswordConfirm = _devPassword }; - var result = await _authServiceClient.ResetPassword(resetModel); - - if (result.Succeeded) - { - Console.WriteLine("Password changed"); - } - else + for (var attempt = 0; attempt < 10; attempt++) { + var result = await _authServiceClient.ResetPassword(resetModel); + if (result.Succeeded) + { + Console.WriteLine("Password changed"); + return; + } + Console.WriteLine("Password not changed"); + var error = result.Errors.FirstOrDefault(); + + Console.WriteLine(error); + if (error is null || !error.Contains("Optimistic concurrency")) + { + return; + } + + await Task.Delay(500); } } }