From 7d0863c3392918a52a81ae262868c150911a733d Mon Sep 17 00:00:00 2001
From: Mouaz Abdelsamad <ma03081@surrey.ac.uk>
Date: Tue, 26 Mar 2024 17:24:19 +0000
Subject: [PATCH] Improvements and Docker

---
 .env                                          | 12 ++++
 FlightMicroservice/Dockerfile                 |  2 -
 FlightMicroservice/FlightMicroservice.csproj  |  2 +-
 FlightMicroservice/Models/Seat.cs             |  4 +-
 FlightMicroservice/Program.cs                 | 26 +++++++--
 FlightMicroservice/Services/SeatService.cs    |  2 +-
 FlightMicroservice/appsettings.json           |  4 +-
 UserMicroservice/.dockerignore                | 30 ++++++++++
 .../Controllers/UserController.cs             | 56 +++++++++++++++----
 UserMicroservice/Dockerfile                   | 23 ++++++++
 UserMicroservice/Models/RegisterModel.cs      |  1 +
 UserMicroservice/Models/User.cs               | 24 +++++---
 UserMicroservice/Program.cs                   |  2 +
 UserMicroservice/Services/AuthService.cs      | 15 +++--
 UserMicroservice/Services/IAuthService.cs     |  2 +-
 UserMicroservice/Services/IUserService.cs     |  3 +-
 UserMicroservice/Services/UserService.cs      | 42 ++++++++++----
 UserMicroservice/UserMicroservice.csproj      |  4 +-
 docker-compose.yml                            | 41 ++++++++++++++
 19 files changed, 242 insertions(+), 53 deletions(-)
 create mode 100644 .env
 create mode 100644 UserMicroservice/.dockerignore
 create mode 100644 UserMicroservice/Dockerfile
 create mode 100644 docker-compose.yml

diff --git a/.env b/.env
new file mode 100644
index 0000000..09923e3
--- /dev/null
+++ b/.env
@@ -0,0 +1,12 @@
+# Image settings
+IMAGE_TAG=0.0.1
+
+# Database configuration
+DB_PORT=3308
+DB_NAME=AspNetCoreDb
+DB_USER=root
+DB_PASSWORD=
+DB_CHARSET=utf8mb4
+
+# Service ports
+USER_MICROSERVICE_PORT=5089  # This port will be used as the JWT issuer
diff --git a/FlightMicroservice/Dockerfile b/FlightMicroservice/Dockerfile
index 9c6a362..8038750 100644
--- a/FlightMicroservice/Dockerfile
+++ b/FlightMicroservice/Dockerfile
@@ -1,5 +1,3 @@
-#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
 FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
 USER app
 WORKDIR /app
diff --git a/FlightMicroservice/FlightMicroservice.csproj b/FlightMicroservice/FlightMicroservice.csproj
index dc33c0f..8b66623 100644
--- a/FlightMicroservice/FlightMicroservice.csproj
+++ b/FlightMicroservice/FlightMicroservice.csproj
@@ -11,7 +11,7 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3" />
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
     <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.1" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
diff --git a/FlightMicroservice/Models/Seat.cs b/FlightMicroservice/Models/Seat.cs
index e8472d3..05391ae 100644
--- a/FlightMicroservice/Models/Seat.cs
+++ b/FlightMicroservice/Models/Seat.cs
@@ -1,4 +1,6 @@
-namespace FlightMicroservice.Models
+using System.Text.Json.Serialization;
+
+namespace FlightMicroservice.Models
 {
     public class Seat
     {
diff --git a/FlightMicroservice/Program.cs b/FlightMicroservice/Program.cs
index 5b9d978..82fabff 100644
--- a/FlightMicroservice/Program.cs
+++ b/FlightMicroservice/Program.cs
@@ -4,13 +4,15 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.IdentityModel.Tokens;
 using System.Text;
+using System.Text.Json.Serialization;
 
 var builder = WebApplication.CreateBuilder(args);
 
-// Add services to the container.
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
+});
 
-builder.Services.AddControllers();
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddSwaggerGen();
 
@@ -32,7 +34,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
         };
     });
 
-// Add dependency injections
+// Dependency injections
 builder.Services.AddScoped<IFlightService, FlightService>();
 builder.Services.AddScoped<ISeatService, SeatService>();
 
@@ -47,6 +49,22 @@ if (app.Environment.IsDevelopment())
 
 app.UseHttpsRedirection();
 
+// Middleware for handling the access token in cookies
+app.Use(async (context, next) =>
+{
+    var accessToken = context.Request.Cookies["AccessToken"];
+    if (!string.IsNullOrEmpty(accessToken))
+    {
+        if (!context.Request.Headers.ContainsKey("Authorization"))
+        {
+            context.Request.Headers.Append("Authorization", "Bearer " + accessToken);
+        }
+    }
+
+    await next();
+});
+
+app.UseAuthentication();
 app.UseAuthorization();
 
 app.MapControllers();
diff --git a/FlightMicroservice/Services/SeatService.cs b/FlightMicroservice/Services/SeatService.cs
index 9a760d8..c3f3618 100644
--- a/FlightMicroservice/Services/SeatService.cs
+++ b/FlightMicroservice/Services/SeatService.cs
@@ -29,7 +29,7 @@ namespace FlightMicroservice.Services
             int affectedRows = dbContext.Seats
                 .Where(seat => seat.Id == id)
                 .ExecuteUpdate(setters => setters
-                    .SetProperty(seat => seat.IsAvailable, true));
+                    .SetProperty(seat => seat.IsAvailable, false));
             
             dbContext.SaveChanges();
             
diff --git a/FlightMicroservice/appsettings.json b/FlightMicroservice/appsettings.json
index 8ffdd21..189dc7f 100644
--- a/FlightMicroservice/appsettings.json
+++ b/FlightMicroservice/appsettings.json
@@ -11,7 +11,7 @@
   },
   "Jwt": {
     "Key": "0QTrd3jToEYj205k01A2R87Hc5YpqDNeywg7JzQpczs=",
-    "Issuer": "http://localhost:5175",
-    "Audience": "http://localhost:5175"
+    "Issuer": "http://localhost:5089",
+    "Audience": "http://localhost:5089"
   }
 }
diff --git a/UserMicroservice/.dockerignore b/UserMicroservice/.dockerignore
new file mode 100644
index 0000000..fe1152b
--- /dev/null
+++ b/UserMicroservice/.dockerignore
@@ -0,0 +1,30 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
+!**/.gitignore
+!.git/HEAD
+!.git/config
+!.git/packed-refs
+!.git/refs/heads/**
\ No newline at end of file
diff --git a/UserMicroservice/Controllers/UserController.cs b/UserMicroservice/Controllers/UserController.cs
index 5db458a..ea86b63 100644
--- a/UserMicroservice/Controllers/UserController.cs
+++ b/UserMicroservice/Controllers/UserController.cs
@@ -1,5 +1,6 @@
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using System.Security.Claims;
 using UserMicroservice.Models;
 using UserMicroservice.Services;
 
@@ -20,18 +21,46 @@ namespace UserMicroservice.Controllers
 
         #region Auth Endpoints
 
-        // POST: api/Users/register
+        // POST: api/User/register
         [HttpPost("register")]
         public IActionResult Register([FromBody] RegisterModel model)
         {
-            User user = _userService.CreateUser(model.Email, model.Username, model.Password);
-            if(user == null)
+            try
+            {
+                User user = _userService.CreateUser(model.Email, model.Username, model.Password, model.UserType);
+                if (user == null)
+                    return BadRequest();
+
+                return authenticateUser(user.Id);
+
+            } catch (InvalidOperationException ex)
+            {
+                return BadRequest(ex.Message);
+            }
+        }
+
+        // POST: api/User/authorize
+        [HttpPost("authorize")]
+        public IActionResult Authorize()
+        {
+            string? refreshToken = Request.Cookies["RefreshToken"];
+            if (string.IsNullOrEmpty(refreshToken))
+                return BadRequest("Refresh token is missing.");
+
+            if (!_authService.ValidateRefreshToken(refreshToken))
+                return Unauthorized("Invalid or expired refresh token.");
+
+            string? userIdString = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
+            if(userIdString == null)
                 return BadRequest();
 
-            return authenticateUser(user);
+            if (!int.TryParse(userIdString, out int userId))
+                return BadRequest("User ID is invalid.");
+
+            return authenticateUser(userId);
         }
 
-        // POST: api/Users/login
+        // POST: api/User/login
         [HttpPost("login")]
         public IActionResult Login([FromBody] LoginModel model)
         {
@@ -39,12 +68,12 @@ namespace UserMicroservice.Controllers
             if(user == null)
                 return Unauthorized();
 
-            return authenticateUser(user);
+            return authenticateUser(user.Id);
         }
 
-        private IActionResult authenticateUser(User user)
+        private IActionResult authenticateUser(int userId)
         {
-            AuthTokenPair authToken = _authService.AuthenticateUser(user);
+            AuthTokenPair authToken = _authService.AuthenticateUser(userId);
             if (authToken == null)
                 return BadRequest();
 
@@ -69,14 +98,17 @@ namespace UserMicroservice.Controllers
             return Ok();
         }
 
-        // POST: api/Users/logout
+        // POST: api/User/logout
         [Authorize]
         [HttpPost("logout")]
         public IActionResult Logout()
         {
             string? refreshToken = Request.Cookies["RefreshToken"];
             if(string.IsNullOrEmpty(refreshToken))
-                return BadRequest();
+                return BadRequest("Refresh token is missing.");
+
+            if (!_authService.ValidateRefreshToken(refreshToken))
+                return Unauthorized("Invalid or expired refresh token.");
 
             _authService.RevokeRefreshToken(refreshToken);
 
@@ -101,7 +133,7 @@ namespace UserMicroservice.Controllers
 
         #endregion
 
-        // GET: api/Users
+        // GET: api/User
         [Authorize]
         [HttpGet()]
         public IActionResult GetUsers()
@@ -113,7 +145,7 @@ namespace UserMicroservice.Controllers
             return Ok(users);
         }
 
-        // GET: api/Users/{id}
+        // GET: api/User/{id}
         [Authorize]
         [HttpGet("{id}")]
         public IActionResult GetUser(int id)
diff --git a/UserMicroservice/Dockerfile b/UserMicroservice/Dockerfile
new file mode 100644
index 0000000..baab33d
--- /dev/null
+++ b/UserMicroservice/Dockerfile
@@ -0,0 +1,23 @@
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER app
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["UserMicroservice.csproj", "."]
+RUN dotnet restore "./UserMicroservice.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "./UserMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./UserMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "UserMicroservice.dll"]
\ No newline at end of file
diff --git a/UserMicroservice/Models/RegisterModel.cs b/UserMicroservice/Models/RegisterModel.cs
index 5634559..8c2e180 100644
--- a/UserMicroservice/Models/RegisterModel.cs
+++ b/UserMicroservice/Models/RegisterModel.cs
@@ -5,5 +5,6 @@
         public required string Username { get; set; }
         public required string Email { get; set; }
         public required string Password { get; set; }
+        public required UserType UserType { get; set; }
     }
 }
diff --git a/UserMicroservice/Models/User.cs b/UserMicroservice/Models/User.cs
index cca8b8e..0d113d6 100644
--- a/UserMicroservice/Models/User.cs
+++ b/UserMicroservice/Models/User.cs
@@ -1,22 +1,30 @@
-namespace UserMicroservice.Models
+using Microsoft.AspNetCore.Identity;
+
+namespace UserMicroservice.Models
 {
     public class User
     {
         // Parameterless constructor for EF Core
         public User() { }
 
-        public User(string userName, string email, string passwordHash)
+        public User(string userName, string email, UserType userType)
         {
             Username = userName;
             Email = email;
-            PasswordHash = passwordHash;
+            Type = userType;
+        }
+
+        public int Id { get; private set; }
+        public string Username { get; private set; }
+        public string Email { get; private set; }
+        public string PasswordHash { get; private set; }
+        public UserType Type { get; private set; } = UserType.CUSTOMER;
+
+        public void SetPasswordHash(IPasswordHasher<User> passwordHasher, string password)
+        {
+            PasswordHash = passwordHasher.HashPassword(this, password);
         }
 
-        public int Id { get; internal set; }
-        public string Username { get; internal set; }
-        public string Email { get; internal set; }
-        public string PasswordHash { get; internal set; }
-        public UserType Type { get; internal set; } = UserType.CUSTOMER;
     }
 
     public enum UserType
diff --git a/UserMicroservice/Program.cs b/UserMicroservice/Program.cs
index 34f4e5e..cbfa241 100644
--- a/UserMicroservice/Program.cs
+++ b/UserMicroservice/Program.cs
@@ -4,6 +4,7 @@ using UserMicroservice.Services;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
 using System.Text;
+using Microsoft.AspNetCore.Identity;
 
 var builder = WebApplication.CreateBuilder(args);
 
@@ -32,6 +33,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
     });
 
 // Add dependency injections
+builder.Services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
 builder.Services.AddScoped<IAuthService, AuthService>();
 builder.Services.AddScoped<IUserService, UserService>();
 
diff --git a/UserMicroservice/Services/AuthService.cs b/UserMicroservice/Services/AuthService.cs
index b8cc84e..36f144c 100644
--- a/UserMicroservice/Services/AuthService.cs
+++ b/UserMicroservice/Services/AuthService.cs
@@ -20,15 +20,15 @@ namespace UserMicroservice.Services
             _context = applicationDbContext;
         }
 
-        public AuthTokenPair AuthenticateUser(User user)
+        public AuthTokenPair AuthenticateUser(int userId)
         {
-            string accessToken = GenerateAccessToken(user);
-            string refreshToken = GenerateRefreshToken(user.Id).Token;
+            string accessToken = GenerateAccessToken(userId);
+            string refreshToken = GenerateRefreshToken(userId).Token;
 
             return new AuthTokenPair(accessToken, refreshToken);
         }
 
-        private string GenerateAccessToken(User user)
+        private string GenerateAccessToken(int userId)
         {
             string? configuredKey = _configuration["Jwt:Key"];
             string? configuredIssuer = _configuration["Jwt:Issuer"];
@@ -41,9 +41,8 @@ namespace UserMicroservice.Services
 
             var claims = new List<Claim>
             {
-                new Claim(JwtRegisteredClaimNames.Sub, user.Username),
                 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
-                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
+                new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
             };
 
             var token = new JwtSecurityToken(
@@ -68,8 +67,8 @@ namespace UserMicroservice.Services
         public bool ValidateRefreshToken(string token)
         {
 
-            RefreshToken refreshToken = _context.RefreshTokens.Single(t => t.Token == token);
-            if (refreshToken != null) { }
+            RefreshToken? refreshToken = _context.RefreshTokens.SingleOrDefault(t => t.Token == token);
+            if (refreshToken != null)
             {
                 if (DateTime.UtcNow <= refreshToken.ExpirationDate)
                 {
diff --git a/UserMicroservice/Services/IAuthService.cs b/UserMicroservice/Services/IAuthService.cs
index 4f30e06..26bff1f 100644
--- a/UserMicroservice/Services/IAuthService.cs
+++ b/UserMicroservice/Services/IAuthService.cs
@@ -4,7 +4,7 @@ namespace UserMicroservice.Services
 {
     public interface IAuthService
     {
-        AuthTokenPair AuthenticateUser(User user);
+        AuthTokenPair AuthenticateUser(int userId);
 
         void RevokeRefreshToken(string token);
 
diff --git a/UserMicroservice/Services/IUserService.cs b/UserMicroservice/Services/IUserService.cs
index b780468..2a4e75b 100644
--- a/UserMicroservice/Services/IUserService.cs
+++ b/UserMicroservice/Services/IUserService.cs
@@ -9,8 +9,9 @@ namespace UserMicroservice.Services
         User? GetUser(int userId);
         User? GetUser(string username, string password);
         List<User> GetUsers();
-        User CreateUser(string email, string userName, string password);
+        User CreateUser(string email, string userName, string password, UserType UserType);
         User UpdateUser(User updatedUser);
         bool DeleteUser(string username);
+        User? GetUserByEmail(string email);
     }
 }
diff --git a/UserMicroservice/Services/UserService.cs b/UserMicroservice/Services/UserService.cs
index 40a3c13..9288ae8 100644
--- a/UserMicroservice/Services/UserService.cs
+++ b/UserMicroservice/Services/UserService.cs
@@ -1,4 +1,5 @@
-using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
 using UserMicroservice.Models;
 
 namespace UserMicroservice.Services
@@ -6,16 +7,18 @@ namespace UserMicroservice.Services
     public class UserService : IUserService
     {
         private readonly ApplicationDbContext _context;
+        private readonly IPasswordHasher<User> _passwordHasher;
 
-        public UserService(ApplicationDbContext context)
+        public UserService(ApplicationDbContext context, IPasswordHasher<User> passwordHasher)
         {
             _context = context;
+            _passwordHasher = passwordHasher;
         }
 
         public bool DeleteUser(string username)
         {
             User? user = _context.Users.SingleOrDefault(user => user.Username == username);
-            if(user == null)
+            if (user == null)
                 return false;
 
             _context.Users.Remove(user);
@@ -24,8 +27,8 @@ namespace UserMicroservice.Services
             return true;
         }
 
-        public List<User> GetUsers() 
-        { 
+        public List<User> GetUsers()
+        {
             List<User> users = _context.Users.ToList();
             return users;
         }
@@ -38,20 +41,39 @@ namespace UserMicroservice.Services
 
         public User? GetUser(int userId)
         {
-            User? user = _context.Users.SingleOrDefault(user=> user.Id ==  userId);
+            User? user = _context.Users.SingleOrDefault(user => user.Id == userId);
             return user;
         }
 
         public User? GetUser(string username, string password)
         {
-            User? user = _context.Users
-                .SingleOrDefault(user => user.Username == username && user.PasswordHash == password);
+            User? user = GetUser(username);
+
+            if (user != null)
+            {
+                // Verify the hashed password with the provided password
+                var verificationResult = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
+                if (verificationResult == PasswordVerificationResult.Success)
+                    return user; // Password is correct, return the user
+            }
+
+            return null; // User not found or password does not match
+        }
+
+        public User? GetUserByEmail(string email)
+        {
+            User? user = _context.Users.SingleOrDefault(user => user.Email == email);
             return user;
         }
 
-        public User CreateUser(string email, string userName, string password)
+        public User CreateUser(string email, string userName, string password, UserType userType)
         {
-            User user = new User(userName, email, password);
+            if (GetUser(userName) != null || GetUserByEmail(email) != null)
+                throw new InvalidOperationException($"A User with the provided credntials already exists");
+
+            User user = new User(userName, email, userType);
+            user.SetPasswordHash(_passwordHasher, password);
+
             _context.Users.Add(user);
             _context.SaveChanges();
 
diff --git a/UserMicroservice/UserMicroservice.csproj b/UserMicroservice/UserMicroservice.csproj
index 3aed6c1..586ea39 100644
--- a/UserMicroservice/UserMicroservice.csproj
+++ b/UserMicroservice/UserMicroservice.csproj
@@ -8,11 +8,11 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
+    <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
     <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
-
-	 <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2" PrivateAssets="All" />
+	<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2" PrivateAssets="All" />
   </ItemGroup>
 
 </Project>
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..83dca10
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,41 @@
+version: '3.8'
+
+services:
+  usermicroservice:
+    build:
+      context: ./UserMicroservice
+    image: usermicroservice:${IMAGE_TAG}
+    ports:
+      - "${USER_MICROSERVICE_PORT}:8080"
+    environment:
+      - ASPNETCORE_ENVIRONMENT=Production
+      - DOTNET_RUNNING_IN_CONTAINER=true
+      - ConnectionStrings__DefaultConnection=Server=host.docker.internal;Port=${DB_PORT};Database=${DB_NAME};User=${DB_USER};Password=${DB_PASSWORD};CharSet=${DB_CHARSET}
+      - Jwt__Key=0QTrd3jToEYj205k01A2R87Hc5YpqDNeywg7JzQpczs=
+      - Jwt__Issuer=http://localhost:${USER_MICROSERVICE_PORT}
+      - Jwt__Audience=http://localhost:${USER_MICROSERVICE_PORT}
+
+  flightmicroservice:
+    build:
+      context: ./FlightMicroservice
+    image: flightmicroservice:${IMAGE_TAG}
+    ports:
+      - "5175:8080"
+    environment:
+      - ASPNETCORE_ENVIRONMENT=Production
+      - DOTNET_RUNNING_IN_CONTAINER=true
+      - ConnectionStrings__DefaultConnection=Server=host.docker.internal;Port=${DB_PORT};Database=${DB_NAME};User=${DB_USER};Password=${DB_PASSWORD};CharSet=${DB_CHARSET}
+      - Jwt__Key=0QTrd3jToEYj205k01A2R87Hc5YpqDNeywg7JzQpczs=
+      - Jwt__Issuer=http://localhost:${USER_MICROSERVICE_PORT}
+      - Jwt__Audience=http://localhost:${USER_MICROSERVICE_PORT}
+
+  client:
+    build:
+      context: ./client
+    image: client:${IMAGE_TAG}
+    ports:
+      - "4200:4200"
+
+
+
+# ... other services
-- 
GitLab