commit c349bab3d35522b4e084ffab4d4d130f35c91f08
Author: Lukáš Vlček <vlcek@net-inout.cz>
Date:   Sun Apr 3 20:36:46 2022 +0200

    Backend - JWT Authentication done

diff --git a/.gitignore b/.gitignore
index 44b114e..97d332a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -479,3 +479,4 @@ fabric.properties
 
 # Android studio 3.1+ serialized cache file
 .idea/caches/build_file_checksums.ser
+.idea
diff --git a/Backend/Backend/Authentication/AuthorizationAttribute.cs b/Backend/Backend/Authentication/AuthorizationAttribute.cs
new file mode 100644
index 0000000..f8d0808
--- /dev/null
+++ b/Backend/Backend/Authentication/AuthorizationAttribute.cs
@@ -0,0 +1,24 @@
+using Core.Entities;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace RestAPI.Authentication;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class AuthorizeAttribute : Attribute, IAuthorizationFilter
+{
+    public void OnAuthorization(AuthorizationFilterContext context)
+    {
+        // skip authorization if action is decorated with [AllowAnonymous] attribute
+        var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
+        if (allowAnonymous)
+            return;
+
+        // authorization
+        var user = (User?) context.HttpContext.Items["User"];
+        if (user == null)
+            context.Result = new JsonResult(new {message = "Unauthorized"})
+                {StatusCode = StatusCodes.Status401Unauthorized};
+    }
+}
\ No newline at end of file
diff --git a/Backend/Backend/Controllers/AuthController.cs b/Backend/Backend/Controllers/AuthController.cs
new file mode 100644
index 0000000..490e77a
--- /dev/null
+++ b/Backend/Backend/Controllers/AuthController.cs
@@ -0,0 +1,57 @@
+using System.Net;
+using System.Security.Authentication;
+using Core.Entities;
+using Core.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Models.Authentication;
+using RestAPI.Controllers.Common;
+
+namespace RestAPI.Controllers;
+
+public class AuthController : CommonControllerBase
+{
+    private readonly IAuthService authService;
+
+    public AuthController(IAuthService authService)
+    {
+        this.authService = authService;
+    }
+
+
+    [AllowAnonymous]
+    [HttpPost("/auth/login")]
+    [ProducesResponseType((int) HttpStatusCode.OK, Type = typeof(LoginResponse))]
+    [ProducesResponseType((int) HttpStatusCode.Forbidden)]
+    public ActionResult<LoginResponse> Login([FromBody] LoginRequest loginData)
+    {
+        try
+        {
+            var loginResponse = authService.Login(loginData.Username, loginData.Password);
+            if (loginResponse == null)
+            {
+                return Forbid();
+            }
+            else
+            {
+                return Ok(loginResponse);
+            }
+        }
+        catch (InvalidCredentialException)
+        {
+            return Forbid();
+        }
+        catch (Exception)
+        {
+            return Problem();
+        }
+    }
+
+    [HttpGet("/auth/test")]
+    [ProducesResponseType((int) HttpStatusCode.OK, Type = typeof(User))]
+    [ProducesResponseType((int) HttpStatusCode.Forbidden)]
+    public ActionResult<User> TestLogged([FromServices] User loggedUser)
+    {
+        return loggedUser;
+    }
+}
\ No newline at end of file
diff --git a/Backend/Backend/Controllers/Common/CommonControllerBase.cs b/Backend/Backend/Controllers/Common/CommonControllerBase.cs
new file mode 100644
index 0000000..d6c7b81
--- /dev/null
+++ b/Backend/Backend/Controllers/Common/CommonControllerBase.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Mvc;
+using RestAPI.Authentication;
+
+namespace RestAPI.Controllers.Common;
+
+[ApiController]
+[Authorize]
+public abstract class CommonControllerBase : ControllerBase
+{
+}
\ No newline at end of file
diff --git a/Backend/Backend/Middleware/JwtMiddleware.cs b/Backend/Backend/Middleware/JwtMiddleware.cs
new file mode 100644
index 0000000..1609e3e
--- /dev/null
+++ b/Backend/Backend/Middleware/JwtMiddleware.cs
@@ -0,0 +1,30 @@
+using Core.Authentication;
+using Core.Services;
+using Microsoft.Extensions.Options;
+
+namespace RestAPI.Middleware;
+
+public class JwtMiddleware
+{
+    private readonly RequestDelegate _next;
+    private readonly JwtConfig _jwtConfig;
+
+    public JwtMiddleware(RequestDelegate next, IOptions<JwtConfig> jwtConfigOptions)
+    {
+        _next = next;
+        _jwtConfig = jwtConfigOptions.Value;
+    }
+
+    public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
+    {
+        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+        var userId = jwtUtils.ValidateJwtToken(token);
+        if (userId != null)
+        {
+            // attach user to context on successful jwt validation
+            context.Items["User"] = userService.GetUserById(userId.Value);
+        }
+
+        await _next(context);
+    }
+}
\ No newline at end of file
diff --git a/Backend/Backend/Program.cs b/Backend/Backend/Program.cs
index d9bffce..5484500 100644
--- a/Backend/Backend/Program.cs
+++ b/Backend/Backend/Program.cs
@@ -1,11 +1,15 @@
 using System.Text.Json.Serialization;
+using Core.Authentication;
 using Core.Contexts;
+using Core.Entities;
 using Core.Seeding;
 using Core.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.EntityFrameworkCore;
 using Microsoft.OpenApi.Models;
+using RestAPI.Middleware;
 using Serilog;
 
-
 var builder = WebApplication.CreateBuilder(args);
 
 // Logging
@@ -28,6 +32,27 @@ builder.Services.AddSwaggerGen(c =>
         new OpenApiInfo {Title = "AnnotationTool", Description = "KIV/ASWI ZČU Plzeň, 2022", Version = "0.1.1"});
 });
 
+// JWT authentication
+var tokenValidationParams = JwtUtils.GetTokenValidationParameters(builder.Configuration);
+builder.Services.AddSingleton(tokenValidationParams);
+builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("JwtConfig"));
+builder.Services.AddAuthentication(options =>
+    {
+        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+    })
+    .AddJwtBearer(jwt =>
+        {
+            jwt.SaveToken = true;
+            jwt.TokenValidationParameters = tokenValidationParams;
+        }
+    );
+
+builder.Services.AddHttpContextAccessor();
+builder.Services.AddScoped<User>(provider =>
+    ((User?) provider.GetRequiredService<IHttpContextAccessor>().HttpContext?.Items["User"]));
+
 // Database
 builder.Services.AddDbContext<DatabaseContext>();
 
@@ -57,7 +82,14 @@ app.UseCors(x => x.WithOrigins(
     .AllowAnyMethod()
     .AllowCredentials());
 
+// JWT Authentication
+app.UseMiddleware<JwtMiddleware>();
+
+/*
+app.UseAuthentication();
 app.UseAuthorization();
+*/
+
 
 app.MapControllers();
 
@@ -87,7 +119,6 @@ using (var scope = app.Services.CreateScope())
         // In production we seed administrator 
         Seeder.SeedProduction(context);
     }
-
 }
 
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/Backend/Backend/RestAPI.csproj b/Backend/Backend/RestAPI.csproj
index 51fe23e..9b79f14 100644
--- a/Backend/Backend/RestAPI.csproj
+++ b/Backend/Backend/RestAPI.csproj
@@ -9,6 +9,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -22,6 +23,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Core\Core.csproj" />
+    <ProjectReference Include="..\Models\Models.csproj" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/Backend/Backend/appsettings.json b/Backend/Backend/appsettings.json
index 418c201..18e2da6 100644
--- a/Backend/Backend/appsettings.json
+++ b/Backend/Backend/appsettings.json
@@ -6,5 +6,9 @@
     }
   },
   "AllowedHosts": "*",
-  "ConnectionString": "Host=localhost:5432;Database=dbo;Username=myuser;Password=password"
+  "ConnectionString": "Host=localhost:5432;Database=dbo;Username=myuser;Password=password",
+  "JwtConfig": {
+    "Secret": "CJgvc9BtwJjnCExzKLB2ndtxERW3YMhqMMPNrBZuTJaskPqRVfZQHRUnbRHH9Ekp5zsaTSeM73vQSKvq48w2u8jTnKUn7Uhk2zBeD9wLz48ssKMppNC9HDTXSk6RWP5PtXfCayJ67mbUWF6aknnteLg4sDKzqYeNYjyw8Jk6",
+    "Issuer": "0x00.server"
+  }
 }
diff --git a/Backend/Core/Authentication/JwtConfig.cs b/Backend/Core/Authentication/JwtConfig.cs
new file mode 100644
index 0000000..06ceaf5
--- /dev/null
+++ b/Backend/Core/Authentication/JwtConfig.cs
@@ -0,0 +1,8 @@
+namespace Core.Authentication;
+
+public class JwtConfig
+{
+    public string Secret { get; set; }
+    public string Issuer { get; set; }
+}
+
diff --git a/Backend/Core/Authentication/JwtUtils.cs b/Backend/Core/Authentication/JwtUtils.cs
new file mode 100644
index 0000000..f12fbb6
--- /dev/null
+++ b/Backend/Core/Authentication/JwtUtils.cs
@@ -0,0 +1,86 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using Core.Entities;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Core.Authentication;
+
+public interface IJwtUtils
+{
+    public string GenerateJwtToken(User user, DateTime? expiration = null);
+    public Guid? ValidateJwtToken(string token);
+}
+
+public class JwtUtils : IJwtUtils
+{
+    private const int EXPIRATION_SECONDS = 8 * 60 * 60; // 8 hod
+
+    private readonly JwtConfig _jwtConfig;
+    private readonly TokenValidationParameters _tokenValidationParameters;
+
+    public JwtUtils(IOptions<JwtConfig> jwtConfig, TokenValidationParameters tokenValidationParameters)
+    {
+        _jwtConfig = jwtConfig.Value;
+        _tokenValidationParameters = tokenValidationParameters;
+    }
+
+    public static TokenValidationParameters GetTokenValidationParameters(ConfigurationManager configuration)
+    {
+        return new TokenValidationParameters
+        {
+            RequireExpirationTime = true,
+            ValidateIssuerSigningKey = false,
+            IssuerSigningKey =
+                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtConfig:Secret"])),
+            ValidateIssuer = false,
+            ValidIssuer = configuration["JwtConfig:Issuer"],
+            ValidateAudience = false,
+            ValidateLifetime = true
+        };
+    }
+
+
+    public string GenerateJwtToken(User user, DateTime? expiration = null)
+    {
+        expiration ??= DateTime.Now.AddHours(10);
+
+        var tokenHandler = new JwtSecurityTokenHandler();
+        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
+        var tokenDescriptor = new SecurityTokenDescriptor
+        {
+            Subject = new ClaimsIdentity(new[] {new Claim("id", user.Id.ToString())}),
+            Expires = expiration,
+            SigningCredentials =
+                new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+        };
+        var token = tokenHandler.CreateToken(tokenDescriptor);
+        return tokenHandler.WriteToken(token);
+    }
+
+    public Guid? ValidateJwtToken(string? token)
+    {
+        if (token == null)
+            return null;
+
+        var tokenHandler = new JwtSecurityTokenHandler();
+        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
+        try
+        {
+            tokenHandler.ValidateToken(token, _tokenValidationParameters, out SecurityToken validatedToken);
+
+            var jwtToken = (JwtSecurityToken) validatedToken;
+            var userId = Guid.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
+
+            // return user id from JWT token if validation successful
+            return userId;
+        }
+        catch
+        {
+            // return null if validation fails
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Backend/Core/Core.csproj b/Backend/Core/Core.csproj
index 4e9b197..d42d515 100644
--- a/Backend/Core/Core.csproj
+++ b/Backend/Core/Core.csproj
@@ -14,10 +14,16 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
+    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" />
     <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
     <PackageReference Include="Serilog" Version="2.10.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
     <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
+    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Models\Models.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/Backend/Core/Services/AuthService/AuthService.cs b/Backend/Core/Services/AuthService/AuthService.cs
new file mode 100644
index 0000000..9ddc136
--- /dev/null
+++ b/Backend/Core/Services/AuthService/AuthService.cs
@@ -0,0 +1,35 @@
+using Core.Authentication;
+using Core.Entities;
+using Models.Authentication;
+
+namespace Core.Services;
+
+public class AuthService : IAuthService
+{
+    private readonly IUserService userService;
+    private readonly IJwtUtils jwtUtils;
+
+    public AuthService(IUserService userService, IJwtUtils jwtUtils)
+    {
+        this.userService = userService;
+        this.jwtUtils = jwtUtils;
+    }
+
+    public LoginResponse? Login(string username, string password)
+    {
+        var user = userService.CheckUsernamePassword(username, password);
+
+        if (user == null)
+        {
+            // user with given credentials not found
+            return null;
+        }
+
+        var expiration = DateTime.Now.AddHours(12);
+        var token = jwtUtils.GenerateJwtToken(user, expiration);
+
+        var loginResult = new LoginResponse(true, token, expiration);
+
+        return loginResult;
+    }
+}
\ No newline at end of file
diff --git a/Backend/Core/Services/AuthService/IAuthService.cs b/Backend/Core/Services/AuthService/IAuthService.cs
new file mode 100644
index 0000000..1469ec9
--- /dev/null
+++ b/Backend/Core/Services/AuthService/IAuthService.cs
@@ -0,0 +1,9 @@
+using Core.Entities;
+using Models.Authentication;
+
+namespace Core.Services;
+
+public interface IAuthService
+{
+    public LoginResponse? Login(string username, string password);
+}
\ No newline at end of file
diff --git a/Backend/Core/Services/Registration.cs b/Backend/Core/Services/Registration.cs
index 30505f5..a8b5c73 100644
--- a/Backend/Core/Services/Registration.cs
+++ b/Backend/Core/Services/Registration.cs
@@ -1,10 +1,6 @@
-﻿using Microsoft.AspNetCore.Builder;
+﻿using Core.Authentication;
+using Microsoft.AspNetCore.Builder;
 using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Core.Services
 {
@@ -13,6 +9,9 @@ namespace Core.Services
         public static void RegisterServices(WebApplicationBuilder builder)
         {
             builder.Services.AddScoped<IUserService, UserServiceEF>();
+            builder.Services.AddScoped<IAuthService, AuthService>();
+            
+            builder.Services.AddScoped<IJwtUtils, JwtUtils>();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/Backend/Core/Services/UserService/IUserService.cs b/Backend/Core/Services/UserService/IUserService.cs
index dfcbce0..7bacc6d 100644
--- a/Backend/Core/Services/UserService/IUserService.cs
+++ b/Backend/Core/Services/UserService/IUserService.cs
@@ -12,7 +12,7 @@ namespace Core.Services
     {
         public User? CreateUser(string username, string name, string surname, string password, ERole role);
         public User? GetUserByUsername(string username);
-        public User? GetUserByGuid(Guid id);
+        public User? GetUserById(Guid id);
         public User UpdateUser(User user, string? username = null, string? name = null, string? surname = null, ERole? role = null);
         public User ChangePassword(User user, string newPassword);
         public User? CheckUsernamePassword(string username, string password);
diff --git a/Backend/Core/Services/UserService/UserServiceEF.cs b/Backend/Core/Services/UserService/UserServiceEF.cs
index 60a14de..35103dc 100644
--- a/Backend/Core/Services/UserService/UserServiceEF.cs
+++ b/Backend/Core/Services/UserService/UserServiceEF.cs
@@ -44,7 +44,7 @@ namespace Core.Services
                 User u = _databaseContext.Users.First(u => u.Username == username);
                 if (!BCrypt.Net.BCrypt.Verify(password, u.Password))
                 {
-                    _logger.Information($"Password for user {username} don't match.");
+                    _logger.Information($"Password for user {username} doesn't match.");
                     return null;
                 }
                 return u;
@@ -93,7 +93,7 @@ namespace Core.Services
             }
         }
 
-        public User? GetUserByGuid(Guid id)
+        public User? GetUserById(Guid id)
         {
             try
             {
diff --git a/Backend/Models/Authentication/LoginRequest.cs b/Backend/Models/Authentication/LoginRequest.cs
new file mode 100644
index 0000000..6393374
--- /dev/null
+++ b/Backend/Models/Authentication/LoginRequest.cs
@@ -0,0 +1,7 @@
+namespace Models.Authentication;
+
+public class LoginRequest
+{
+    public string Username { get; set; }
+    public string Password { get; set; }
+}   
\ No newline at end of file
diff --git a/Backend/Models/Authentication/LoginResponse.cs b/Backend/Models/Authentication/LoginResponse.cs
new file mode 100644
index 0000000..8441bd4
--- /dev/null
+++ b/Backend/Models/Authentication/LoginResponse.cs
@@ -0,0 +1,19 @@
+namespace Models.Authentication;
+
+public class LoginResponse
+{
+    public LoginResponse(bool ok, string token, DateTime expiration)
+    {
+        Ok = ok;
+        Token = token;
+        Expiration = expiration;
+    }
+
+    public LoginResponse()
+    {
+    }
+
+    public bool Ok { get; set; }
+    public string Token { get; set; }
+    public DateTime Expiration { get; set; }
+}
\ No newline at end of file
diff --git a/webapp/.idea/.gitignore b/webapp/.idea/.gitignore
deleted file mode 100644
index b58b603..0000000
--- a/webapp/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/webapp/.idea/codeStyles/Project.xml b/webapp/.idea/codeStyles/Project.xml
deleted file mode 100644
index 008b81d..0000000
--- a/webapp/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
-  <code_scheme name="Project" version="173">
-    <HTMLCodeStyleSettings>
-      <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
-      <option name="HTML_QUOTE_STYLE" value="Single" />
-      <option name="HTML_ENFORCE_QUOTES" value="true" />
-    </HTMLCodeStyleSettings>
-    <JSCodeStyleSettings version="0">
-      <option name="FORCE_SEMICOLON_STYLE" value="true" />
-      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
-      <option name="USE_DOUBLE_QUOTES" value="false" />
-      <option name="FORCE_QUOTE_STYlE" value="true" />
-      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
-      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
-      <option name="SPACES_WITHIN_IMPORTS" value="true" />
-    </JSCodeStyleSettings>
-    <TypeScriptCodeStyleSettings version="0">
-      <option name="FORCE_SEMICOLON_STYLE" value="true" />
-      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
-      <option name="USE_DOUBLE_QUOTES" value="false" />
-      <option name="FORCE_QUOTE_STYlE" value="true" />
-      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
-      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
-      <option name="SPACES_WITHIN_IMPORTS" value="true" />
-    </TypeScriptCodeStyleSettings>
-    <VueCodeStyleSettings>
-      <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
-      <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
-    </VueCodeStyleSettings>
-    <codeStyleSettings language="HTML">
-      <option name="SOFT_MARGINS" value="90" />
-      <indentOptions>
-        <option name="CONTINUATION_INDENT_SIZE" value="4" />
-      </indentOptions>
-    </codeStyleSettings>
-    <codeStyleSettings language="JavaScript">
-      <option name="SOFT_MARGINS" value="90" />
-    </codeStyleSettings>
-    <codeStyleSettings language="TypeScript">
-      <option name="SOFT_MARGINS" value="90" />
-    </codeStyleSettings>
-    <codeStyleSettings language="Vue">
-      <option name="SOFT_MARGINS" value="90" />
-      <indentOptions>
-        <option name="INDENT_SIZE" value="4" />
-        <option name="TAB_SIZE" value="4" />
-      </indentOptions>
-    </codeStyleSettings>
-  </code_scheme>
-</component>
\ No newline at end of file
diff --git a/webapp/.idea/codeStyles/codeStyleConfig.xml b/webapp/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123..0000000
--- a/webapp/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
-  <state>
-    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
-  </state>
-</component>
\ No newline at end of file
diff --git a/webapp/.idea/inspectionProfiles/Project_Default.xml b/webapp/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 03d9549..0000000
--- a/webapp/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<component name="InspectionProjectProfileManager">
-  <profile version="1.0">
-    <option name="myName" value="Project Default" />
-    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
-  </profile>
-</component>
\ No newline at end of file
diff --git a/webapp/.idea/modules.xml b/webapp/.idea/modules.xml
deleted file mode 100644
index 57904ce..0000000
--- a/webapp/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/.idea/webapp.iml" filepath="$PROJECT_DIR$/.idea/webapp.iml" />
-    </modules>
-  </component>
-</project>
\ No newline at end of file
diff --git a/webapp/.idea/prettier.xml b/webapp/.idea/prettier.xml
deleted file mode 100644
index 727b8b5..0000000
--- a/webapp/.idea/prettier.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="PrettierConfiguration">
-    <option name="myRunOnSave" value="true" />
-    <option name="myRunOnReformat" value="true" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/webapp/.idea/vcs.xml b/webapp/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/webapp/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/webapp/.idea/webapp.iml b/webapp/.idea/webapp.iml
deleted file mode 100644
index 0c8867d..0000000
--- a/webapp/.idea/webapp.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="WEB_MODULE" version="4">
-  <component name="NewModuleRootManager">
-    <content url="file://$MODULE_DIR$">
-      <excludeFolder url="file://$MODULE_DIR$/temp" />
-      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
-      <excludeFolder url="file://$MODULE_DIR$/tmp" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
-</module>
\ No newline at end of file
