Added authentication

feature/TransactionUI
William Lewis 6 months ago
parent 15657df008
commit 68aa1e72a0

4
.gitignore vendored

@ -0,0 +1,4 @@
# Docker Container Persistant files
Postgres/pg-db_data/*
Postgres/pg-admin_data/*

@ -0,0 +1,3 @@
POSTGRES_PASSWORD=nqA3UV3CliLLHpLL
PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=3254

@ -0,0 +1,33 @@
version: '3'
services:
database:
container_name: activeallocator-pg-db
image: 'postgres:15'
ports:
- 15432:5432
env_file:
- .env
networks:
- postgres-network
volumes:
- ./pg-db_data/:/var/lib/postgresql/data/
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
management_interface:
container_name: activeallocator-pg-admin
image: 'dpage/pgadmin4:7.1'
ports:
- 15433:80
env_file:
- .env
depends_on:
- database
networks:
- postgres-network
volumes:
- ./pg-admin_data/:/var/lib/pgadmin/
networks:
postgres-network:
driver: bridge

@ -0,0 +1,3 @@
-- Create the DB if it doesn't already exist
SELECT 'CREATE DATABASE AADB'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'AADB')\gexec

@ -1,3 +1,67 @@
# ActiveAllocator
# Active Allocator - React & .NET Core API
An API and Web App for managing budgets with allocations (buckets of funds based on history) and interfacing with Banks, and FireFlyIII.
An API and Web App for managing budgets with allocations (buckets of funds based on history) and interfacing with Banks, and FireFlyIII.
I started with [this](https://travis.media/how-to-create-react-app-net-api-vscode/) tutorial.
[Here](https://jasonwatmore.com/post/2022/03/15/net-6-crud-api-example-and-tutorial#user-service-cs) is another one that is handy for good structure.
Reference for adding JWT authentication is [here](https://jasonwatmore.com/net-7-csharp-jwt-authentication-tutorial-without-aspnet-core-identity#program-cs).
[Docker Repository Usage](https://www.ionos.com/digitalguide/server/know-how/setting-up-a-docker-repository/)
## Dev Environment Setup
On Archlinux install the following to use dotnet: ```sudo pacman -Sy dotnet-sdk dotnet-runtime aspnet-runtime```.
For Archlinux install docker with ```sudo pacman -Sy docker docker-compose```.
Then run ```systemctl start docker.service``` and ```systemctl enable docker.service``` to start and enable on boot the docker engine.
Install React dependencies if needed - ```npm install``` in the ClientApp directory.
### Download 'Reverse Engineering' Tools
```
dotnet tool install --global dotnet-ef
dotnet tool install --global dotnet-aspnet-codegenerator
```
Confirm it is working with ```dotnet ef```.
## Scaffolding
1. Create Entity (or Model) for object.
2. Add ```DbSet<object>``` entry in your DbContext.
3. Migration
4. Endpoints (if needed)
To add Migration for new entity:
```
dotnet ef migrations add InitialCreate
dotnet ef database update
```
For generating API endpoints for an entity called ```Note```:
```
$HOME/.dotnet/tools/dotnet-aspnet-codegenerator controller -name NotesController -async -api -m Note -dc MyDBContext --relativeFolderPath Controllers
```
## Deploying
### Create Docker Image
Running ```sudo dotnet publish``` will create a docker image targetting linux x64 with the "DefaultContainer" profile and image name of "cavaliercraftsmen.site". Edit the .csproj file to change the version of the image.
To create a docker image explicitly run ```sudo dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -p:ContainerImageName=cavaliercraftsmen.site``` in the same folder as the dotnet solution. Because we added the ```Microsoft.NET.Build.Containers``` package, we don't actually need to use the Dockerfile.
### Pushing Docker Image
You will have to be logged in. ```sudo docker login gitea.veritablevalor.com```
First tag ```sudo docker tag <image_id> gitea.veritablevalor.com/blizliam/cavaliercraftsmen.site:latest``` (check that the version is right).
Then push ```sudo docker push gitea.veritablevalor.com/blizliam/cavaliercraftsmen.site:latest```.
## Using API
API should be something like - https://localhost:7260/swagger

@ -0,0 +1,5 @@
namespace active_allocator.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }

@ -0,0 +1,22 @@
namespace active_allocator.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using active_allocator.Entities;
[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 };
}
}

@ -0,0 +1,26 @@
namespace active_allocator.Authorization;
using active_allocator.Services;
public class JwtMiddleware
{
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var userId = jwtUtils.ValidateToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetById(userId.Value);
}
await _next(context);
}
}

@ -0,0 +1,72 @@
namespace active_allocator.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using active_allocator.Entities;
using active_allocator.Helpers;
public interface IJwtUtils
{
public string GenerateToken(User user);
public int? ValidateToken(string token);
}
public class JwtUtils : IJwtUtils
{
private readonly AppSettings _appSettings;
public JwtUtils(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public string GenerateToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public int? ValidateToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.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;
}
}
}

@ -0,0 +1,73 @@
namespace active_allocator.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using active_allocator.Models.Users;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private IUserService _userService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public UsersController(
IUserService userService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_userService = userService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate(AuthenticateRequest model)
{
var response = _userService.Authenticate(model);
return Ok(response);
}
[AllowAnonymous]
[HttpPost("register")]
public IActionResult Register(RegisterRequest model)
{
_userService.Register(model);
return Ok(new { message = "Registration successful" });
}
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var user = _userService.GetById(id);
return Ok(user);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]UpdateRequest model)
{
_userService.Update(id, model);
return Ok(new { message = "User updated" });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_userService.Delete(id);
return Ok(new { message = "User deleted" });
}
}

@ -0,0 +1,7 @@
namespace active_allocator.Entities;
public enum Role
{
Admin,
User
}

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace active_allocator.Entities;
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Role Role { get; set; }
[JsonIgnore]
public string PasswordHash { get; set; }
}

@ -0,0 +1,17 @@
namespace active_allocator.Helpers;
using System.Globalization;
// custom exception class for throwing application specific exceptions (e.g. for validation)
// that can be caught and handled within the application
public class AppException : Exception
{
public AppException() : base() {}
public AppException(string message) : base(message) { }
public AppException(string message, params object[] args)
: base(String.Format(CultureInfo.CurrentCulture, message, args))
{
}
}

@ -0,0 +1,6 @@
namespace active_allocator.Helpers;
public class AppSettings
{
public required string Secret { get; set; }
}

@ -0,0 +1,33 @@
namespace active_allocator.Helpers;
using AutoMapper;
using active_allocator.Entities;
using active_allocator.Models.Users;
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
// User -> AuthenticateResponse
CreateMap<User, AuthenticateResponse>();
// RegisterRequest -> User
CreateMap<RegisterRequest, User>();
// UpdateRequest -> User
CreateMap<UpdateRequest, User>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
// ignore null role
if (x.DestinationMember.Name == "Role" && src.Role == null) return false;
return true;
}
));
}
}

@ -0,0 +1,22 @@
namespace active_allocator.Helpers;
using Microsoft.EntityFrameworkCore;
using active_allocator.Entities;
public class DataContext : DbContext
{
//protected readonly IConfiguration Configuration;
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
//Configuration = configuration;
}
/*protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// in memory database used for simplicity, change to a real db for production applications
options.UseInMemoryDatabase("TestDb");
}*/
public DbSet<User> Users { get; set; }
}

@ -0,0 +1,54 @@
namespace active_allocator.Helpers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ErrorHandlerMiddleware(RequestDelegate next, ILogger<ErrorHandlerMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
switch (error)
{
case AppException e:
// custom application error
response.StatusCode = (int)HttpStatusCode.BadRequest;
break;
case KeyNotFoundException e:
// not found error
response.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
// unhandled error
_logger.LogError(error, error.Message);
response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
}
}
}

@ -0,0 +1,65 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using active_allocator.Helpers;
#nullable disable
namespace active_allocator.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20231117033912_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace active_allocator.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Username = table.Column<string>(type: "text", nullable: false),
FirstName = table.Column<string>(type: "text", nullable: false),
LastName = table.Column<string>(type: "text", nullable: false),
Email = table.Column<string>(type: "text", nullable: false),
Role = table.Column<int>(type: "integer", nullable: false),
PasswordHash = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

@ -0,0 +1,62 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using active_allocator.Helpers;
#nullable disable
namespace active_allocator.Migrations
{
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,12 @@
namespace active_allocator.Models.Users;
using System.ComponentModel.DataAnnotations;
public class AuthenticateRequest
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}

@ -0,0 +1,10 @@
namespace active_allocator.Models.Users;
public class AuthenticateResponse
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Token { get; set; }
}

@ -0,0 +1,28 @@
namespace active_allocator.Models.Users;
using System.ComponentModel.DataAnnotations;
using active_allocator.Entities;
public class RegisterRequest
{
[Required]
public string Username { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
[EnumDataType(typeof(Role))]
public string Role { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[MinLength(6)]
public string Password { get; set; }
}

@ -0,0 +1,35 @@
namespace active_allocator.Models.Users;
using System.ComponentModel.DataAnnotations;
using active_allocator.Entities;
public class UpdateRequest
{
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[EnumDataType(typeof(Role))]
public string Role { get; set; }
[EmailAddress]
public string Email { get; set; }
// treat empty string as null for password fields to
// make them optional in front end apps
private string _password;
[MinLength(6)]
public string Password
{
get => _password;
set => _password = replaceEmptyWithNull(value);
}
// helpers
private string replaceEmptyWithNull(string value)
{
// replace empty string with null to make field optional
return string.IsNullOrEmpty(value) ? null : value;
}
}

@ -1,27 +1,62 @@
var builder = WebApplication.CreateBuilder(args);
using active_allocator.Authorization;
using active_allocator.Helpers;
using Microsoft.EntityFrameworkCore;
// Add services to the container.
using active_allocator.Services;
using Microsoft.OpenApi.Models;
builder.Services.AddControllersWithViews();
internal class Program
{
private static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Add services to the container.
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
builder.Services.AddControllersWithViews();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "AA API", Version = "v1" });
});
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddDbContext<DataContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("PSQLConnection")));
// Configure strongly typed settings object
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IJwtUtils, JwtUtils>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSwagger();
app.UseSwaggerUI();
app.UseRouting();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// custom jwt auth middleware
app.UseMiddleware<JwtMiddleware>();
// global error handler
app.UseMiddleware<ErrorHandlerMiddleware>();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.MapFallbackToFile("index.html");
app.Run();
app.Run();
}
}

@ -0,0 +1,114 @@
namespace active_allocator.Services;
using AutoMapper;
using BCrypt.Net;
using active_allocator.Authorization;
using active_allocator.Entities;
using active_allocator.Helpers;
using active_allocator.Models.Users;
public interface IUserService
{
AuthenticateResponse Authenticate(AuthenticateRequest model);
void Register(RegisterRequest model);
IEnumerable<User> GetAll();
User GetById(int id);
void Update(int id, UpdateRequest model);
void Delete(int id);
}
public class UserService : IUserService
{
private DataContext _context;
private IJwtUtils _jwtUtils;
private readonly IMapper _mapper;
public UserService(
DataContext context,
IMapper mapper,
IJwtUtils jwtUtils)
{
_context = context;
_mapper = mapper;
_jwtUtils = jwtUtils;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
{
var user = _context.Users.SingleOrDefault(x => x.Username == model.Username);
// validate
if (user == null || !BCrypt.Verify(model.Password, user.PasswordHash))
throw new AppException("Username or password is incorrect");
// authentication successful
var response = _mapper.Map<AuthenticateResponse>(user);
response.Token = _jwtUtils.GenerateToken(user);
return response;
}
public void Register(RegisterRequest model)
{
// validate username
if (_context.Users.Any(x => x.Username == model.Username))
throw new AppException("Username '" + model.Username + "' is already taken");
// validate email
if (_context.Users.Any(x => x.Email == model.Email))
throw new AppException("Email '" + model.Email + "' is already taken");
// map model to new user object
var user = _mapper.Map<User>(model);
// hash password
user.PasswordHash = BCrypt.HashPassword(model.Password);
// save user
_context.Users.Add(user);
_context.SaveChanges();
}
public IEnumerable<User> GetAll()
{
return _context.Users;
}
public User GetById(int id)
{
return getUser(id);
}
public void Update(int id, UpdateRequest model)
{
var user = getUser(id);
// validate
if (model.Email != user.Email && _context.Users.Any(x => x.Email == model.Email))
throw new AppException("User with the email '" + model.Email + "' already exists");
// hash password if it was entered
if (!string.IsNullOrEmpty(model.Password))
user.PasswordHash = BCrypt.HashPassword(model.Password);
// copy model to user and save
_mapper.Map(model, user);
_context.Users.Update(user);
_context.SaveChanges();
}
public void Delete(int id)
{
var user = getUser(id);
_context.Users.Remove(user);
_context.SaveChanges();
}
// helper methods
private User getUser(int id)
{
var user = _context.Users.Find(id);
if (user == null) throw new KeyNotFoundException("User not found");
return user;
}
}

@ -14,8 +14,30 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<ContainerImageName>activeallocator</ContainerImageName>
<PublishProfile>DefaultContainer</PublishProfile>
<ContainerImageTags>1.0.0;latest</ContainerImageTags>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="BCrypt.Net" Version="0.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.32.1" />
<PackageReference Include="Microsoft.NET.Build.Containers" Version="7.0.400" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.13" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1" />
</ItemGroup>
<ItemGroup>

@ -1,4 +1,7 @@
{
"ConnectionStrings": {
"PSQLConnection": "Server=localhost;Port=15432;Database=aadb;User Id=postgres;Password=nqA3UV3CliLLHpLL"
},
"Logging": {
"LogLevel": {
"Default": "Information",
@ -6,5 +9,8 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"AppSettings": {
"Secret": "5de80277015f9fd564c4d1cc2cf827dbb1774cd66e7d79aa258d9c35a9f67f32fc6cf0dc24244242bd9501288e0fd69e315b"
}
}

@ -0,0 +1,11 @@
version: '3.4'
services:
activeallocator-site:
image: activeallocator:1.0.0
container_name: activeallocator
#build:
#context: .
#dockerfile: MoviesAPI/Dockerfile
ports:
- 80:80
Loading…
Cancel
Save