Add views project

This commit is contained in:
Wvader 2022-09-19 03:50:15 +01:00
parent 56a56c6c38
commit 1e63d7e830
211 changed files with 149928 additions and 390 deletions

View File

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BlueWest\BlueWest.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="obj\Debug\net6.0\apphost.exe" />
<Content Include="obj\Debug\net6.0\BlueWest.Api.Gateway.csproj.FileListAbsolute.txt" />
<Content Include="obj\rider.project.restore.info" />
</ItemGroup>
</Project>

View File

@ -1,18 +0,0 @@
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["BlueWest.Api.Gateway/BlueWest.Api.Gateway.csproj", "BlueWest.Api.Gateway/"]
RUN dotnet restore "BlueWest.Api.Gateway/BlueWest.Api.Gateway.csproj"
COPY . .
WORKDIR "/src/BlueWest.Api.Gateway"
RUN dotnet build "BlueWest.Api.Gateway.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "BlueWest.Api.Gateway.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "BlueWest.Api.Gateway.dll"]

View File

@ -1,11 +0,0 @@
// See https://aka.ms/new-console-template for more information
using BlueWest.Core;
using BlueWest.Tools;
EventManager EventManager = new EventManager(new Dictionary<Type, List<EventListenerBase>>(10000));
ThreadServer _threadServer;
System.Threading.Thread.Sleep(2500);
_threadServer = new ThreadServer(EventManager);
_threadServer.Init();

View File

@ -32,30 +32,16 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Context\Templates\AddToTemplate.csx" />
<AdditionalFiles Include="Context\Templates\UpdateTemplate.csx" />
<AdditionalFiles Include="Context\Templates\GetOneByTemplate.csx" />
<AdditionalFiles Include="Context\Templates\GetManyTemplate.csx" />
<AdditionalFiles Include="Context\Templates\GetOneTemplate.csx" />
<AdditionalFiles Include="Context\Templates\GetListTemplate.csx" />
<AdditionalFiles Include="Context\Templates\AddToListTemplate.csx" />
<AdditionalFiles Include="Context\Templates\GetOneFromListTemplate.csx" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\BlueWest.Data.Application\BlueWest.Data.Application.csproj" /> <ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
<ProjectReference Include="..\BlueWest.Data.Capital\BlueWest.Data.Capital.csproj" />
<ProjectReference Include="..\BlueWest\BlueWest.csproj" /> <ProjectReference Include="..\BlueWest\BlueWest.csproj" />
<ProjectReference Include="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="obj\rider.project.restore.info" /> <Content Include="obj\rider.project.restore.info" />
</ItemGroup> </ItemGroup>
<Import Project="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.props" />
</Project> </Project>

View File

@ -0,0 +1,13 @@
namespace BlueWest.WebApi.Configuration
{
internal class BlueWestConnectionString
{
public string Redis { get; set; }
public string MySql { get; set; }
}
internal class ConnectionStringDocker : BlueWestConnectionString { }
internal class ConnectionStringNoDocker : BlueWestConnectionString { }
}

View File

@ -12,6 +12,8 @@ namespace BlueWest.WebApi.Controllers;
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[EnableCors(Constants.CorsPolicyName)] [EnableCors(Constants.CorsPolicyName)]
[ServiceFilter(typeof(SessionAuthorizationFilter))]
public class ApplicationController : ControllerBase public class ApplicationController : ControllerBase
{ {
[HttpGet] [HttpGet]

View File

@ -1,3 +1,4 @@
using BlueWest.Domain;
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.Context; using BlueWest.WebApi.Context;
using BlueWest.WebApi.Context.Users; using BlueWest.WebApi.Context.Users;
@ -16,6 +17,7 @@ namespace BlueWest.WebApi.Controllers
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[EnableCors(Constants.CorsPolicyName)] [EnableCors(Constants.CorsPolicyName)]
[ServiceFilter(typeof(SessionAuthorizationFilter))]
public class ApplicationUserController : ControllerBase public class ApplicationUserController : ControllerBase
{ {

View File

@ -56,7 +56,7 @@ namespace BlueWest.WebApi.Controllers;
[HttpPost("login")] [HttpPost("login")]
public async Task<ActionResult<IdentityResult>> GetSessionToken(LoginRequest loginViewModel) public async Task<ActionResult<IdentityResult>> GetSessionToken(LoginRequest loginViewModel)
{ {
var (success, sessionToken, identity) = await _authManager.GetSessionTokenId(loginViewModel); var (success, sessionToken, _) = await _authManager.GetSessionTokenIdByLoginRequest(loginViewModel);
if (success) if (success)
{ {
@ -65,25 +65,6 @@ namespace BlueWest.WebApi.Controllers;
} }
return Problem(); return Problem();
} }
/// <summary>
/// Gets a bearer token
/// </summary>
/// <param name="loginViewModel"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("bearer")]
public async Task<ActionResult<IdentityResult>> GetBearerBySessionId(string sessionId)
{
var (success, bearer) = await _authManager.GetBearerTokenBySessionTokenId(sessionId);
if (success)
{
return Ok(new {bearer});
}
return new UnauthorizedObjectResult(new {message = "The provided sessionId didn't return a valid Token."});
}
/// <summary> /// <summary>

View File

@ -1,11 +1,5 @@
using System; using BlueWest.Domain;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -20,6 +14,8 @@ namespace BlueWest.WebApi.Controllers
[Route("[controller]")] [Route("[controller]")]
[Authorize(Policy = SessionConstants.ApiNamePolicy)] [Authorize(Policy = SessionConstants.ApiNamePolicy)]
[EnableCors(Constants.CorsPolicyName)] [EnableCors(Constants.CorsPolicyName)]
[ServiceFilter(typeof(SessionAuthorizationFilter))]
// [Authorize(Roles = "Administrator")] // [Authorize(Roles = "Administrator")]
public class CountryController : ControllerBase public class CountryController : ControllerBase
{ {
@ -38,6 +34,11 @@ namespace BlueWest.WebApi.Controllers
#endregion #endregion
public override AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName)
{
return base.AcceptedAtAction(actionName, controllerName);
}
#region GetCountries #region GetCountries
/// <summary> /// <summary>

View File

@ -1,8 +1,5 @@
using System; using BlueWest.Domain;
using System.Linq;
using System.Linq.Expressions;
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -20,6 +17,7 @@ namespace BlueWest.WebApi.Controllers
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[EnableCors(Constants.CorsPolicyName)] [EnableCors(Constants.CorsPolicyName)]
[ServiceFilter(typeof(SessionAuthorizationFilter))]
// [Authorize(Roles = "Administrator")] // [Authorize(Roles = "Administrator")]
public partial class CurrencyController : ControllerBase public partial class CurrencyController : ControllerBase

View File

@ -1,6 +1,6 @@
using System; using System;
using BlueWest.Domain;
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -19,6 +19,7 @@ namespace BlueWest.WebApi.Controllers;
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
//[Authorize(Roles = "Administrator")] //[Authorize(Roles = "Administrator")]
[EnableCors(Constants.CorsPolicyName)] [EnableCors(Constants.CorsPolicyName)]
[ServiceFilter(typeof(SessionAuthorizationFilter))]
public class FinanceController : ControllerBase public class FinanceController : ControllerBase
{ {

View File

@ -1,9 +1,7 @@
using System; using System.Linq;
using System.Collections.Generic; using BlueWest.Domain;
using System.Collections.Immutable; using BlueWest.Domain;
using System.Linq;
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -1,8 +1,9 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlueWest.Domain;
using BlueWest.Domain;
using BlueWest.Tools; using BlueWest.Tools;
using BlueWest.WebApi.Context; using BlueWest.WebApi.Context;
using BlueWest.WebApi.EF;
namespace BlueWest.WebApi.Interfaces namespace BlueWest.WebApi.Interfaces
{ {

View File

@ -0,0 +1,21 @@
syntax = "proto3";
option csharp_namespace = "BlueWest.Streaming";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}

View File

@ -10,13 +10,6 @@ namespace BlueWest.WebApi
/// </summary> /// </summary>
public interface ISessionCache : IHostedService public interface ISessionCache : IHostedService
{ {
/// <summary>
/// Gets a Bearer By Access Token Id
/// </summary>
/// <param name="sessionTokenId"></param>
/// <returns></returns>
Task<string> GetBearerByAccessTokenId(string sessionTokenId);
/// <summary> /// <summary>
/// Gets a Session Token by Id. /// Gets a Session Token by Id.
/// </summary> /// </summary>
@ -29,13 +22,28 @@ namespace BlueWest.WebApi
/// <param name="token"></param> /// <param name="token"></param>
Task AddSessionToken(SessionToken token); Task AddSessionToken(SessionToken token);
/// <summary>
/// Check for validity of the session
/// </summary>
/// <param name="sessionTokenId"></param>
/// <returns></returns>
Task<bool> IsSessionValidAsync(string sessionTokenId);
/// <summary>
/// Checks if the session is valid
/// </summary>
/// <param name="sessionTokenId"></param>
/// <returns></returns>
bool IsSessionValid(string sessionTokenId);
/// <summary> /// <summary>
/// Save Cache /// Save Cache
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task SaveAsync(); Task SaveAsync();
/// <summary> /// <summary>
/// Save Cache /// Save Cache
/// </summary> /// </summary>

View File

@ -0,0 +1,73 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace BlueWest.WebApi.Controllers;
/// <summary>
/// Use this filter where an authorized session is need to navigate in the frontend.
/// </summary>
public class SessionAuthorizationFilter :
IAsyncAuthorizationFilter,
IAuthorizationFilter
{
private ISessionCache _sessionCache { get;}
/// <summary>
/// Session Authorization filter
/// </summary>
/// <param name="sessionCache"></param>
public SessionAuthorizationFilter(ISessionCache sessionCache)
{
_sessionCache = sessionCache;
}
/// <summary>
/// Empty Attribute Constructor
/// </summary>
public SessionAuthorizationFilter() { }
/// <summary>
/// First verification executed before each request
/// </summary>
/// <param name="context"></param>
public void OnAuthorization(AuthorizationFilterContext context)
{
var sessionToken = context.HttpContext.Request.Headers.GetSessionTokenHeader();
if (sessionToken == String.Empty)
{
context.Result = new ForbidResult("Invalid header");
}
var sessionIsValid = _sessionCache.IsSessionValid(sessionToken);
if (!sessionIsValid)
{
context.Result = new ForbidResult("Session is not valid.");
}
}
/// <summary>
/// First verification executed before each request
/// </summary>
/// <param name="context"></param>
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var sessionToken = context.HttpContext.Request.Headers.GetSessionTokenHeader();
if (sessionToken == String.Empty)
{
context.Result = new ForbidResult("Invalid header");
}
var sessionIsValid = await _sessionCache.IsSessionValidAsync(sessionToken);
if (!sessionIsValid)
{
context.Result = new ForbidResult("Session is not valid.");
}
}
}

View File

@ -5,8 +5,10 @@ namespace BlueWest.WebApi
internal static class SessionConstants internal static class SessionConstants
{ {
public static TimeSpan DefaultValidForSpan = TimeSpan.FromHours(24); public static TimeSpan DefaultSessionMaxAge = TimeSpan.FromHours(24);
public const string ApiNamePolicy = "ApiUser"; public const string ApiNamePolicy = "ApiUser";
public const string SessionTokenHeaderName = "x-bw2-auth";
public const string CookieDomain = "http://localhost:5173";
} }
} }

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http;
namespace BlueWest.WebApi.Controllers
{
public static class SessionExtensions
{
/// <summary>
/// Check for the session token header
/// </summary>
/// <param name="headerDictionary"></param>
/// <returns></returns>
public static string GetSessionTokenHeader(this IHeaderDictionary headerDictionary)
{
if (headerDictionary.ContainsKey(SessionConstants.SessionTokenHeaderName))
{
return headerDictionary[SessionConstants.SessionTokenHeaderName];
}
return string.Empty;
}
}
}

View File

@ -12,15 +12,15 @@ namespace BlueWest.WebApi.Session
/// <summary> /// <summary>
/// Session Provider Context /// Session Provider Context
/// </summary> /// </summary>
public sealed class SessionDataService : IHostedService, ISessionCache public sealed class SessionManager : ISessionCache
{ {
private readonly RedisConnectionProvider _provider; private readonly RedisConnectionProvider _provider;
private RedisCollection<SessionToken> _sessionTokens; private readonly RedisCollection<SessionToken> _sessionTokens;
/// <summary> /// <summary>
/// Index Creation Device /// Index Creation Device
/// </summary> /// </summary>
/// <param name="provider">Redis connection</param> /// <param name="provider">Redis connection</param>
public SessionDataService( public SessionManager(
RedisConnectionProvider provider) RedisConnectionProvider provider)
{ {
_provider = provider; _provider = provider;
@ -30,7 +30,7 @@ namespace BlueWest.WebApi.Session
/// <summary> /// <summary>
/// Empty constructor /// Empty constructor
/// </summary> /// </summary>
public SessionDataService() { } public SessionManager() { }
/// <summary> /// <summary>
/// Get a session token by the respective Id. /// Get a session token by the respective Id.
@ -58,6 +58,7 @@ namespace BlueWest.WebApi.Session
await _sessionTokens.SaveAsync(); await _sessionTokens.SaveAsync();
} }
/// <summary> /// <summary>
/// Save session data /// Save session data
/// </summary> /// </summary>
@ -65,32 +66,52 @@ namespace BlueWest.WebApi.Session
{ {
_sessionTokens.Save(); _sessionTokens.Save();
} }
/// <summary> /// <summary>
/// Gets a Bearer By Access Token Id /// Gets a Bearer By Access Token Id
/// </summary> /// </summary>
/// <param name="sessionTokenId"></param> /// <param name="sessionTokenId"></param>
public async Task<string> GetBearerByAccessTokenId(string sessionTokenId) public async Task<bool> IsSessionValidAsync(string sessionTokenId)
{ {
var accessToken = await _sessionTokens.Where(t => t.Id == sessionTokenId) var accessToken = await _sessionTokens
.Where(t => t.Id == sessionTokenId)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (accessToken == null) return string.Empty; if (accessToken == null) return false;
if (accessToken.IsValid) if (accessToken.IsValid)
{ {
var createdDate = DateTime.UnixEpoch.AddMilliseconds(accessToken.CreatedDate); // Check if it's not expired
if (createdDate.AddMilliseconds(accessToken.ValidFor) < DateTime.Now) var expirationDate = accessToken.CreatedDate + accessToken.ValidFor;
{ var timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
accessToken.IsValid = false; if (expirationDate >= timeNow) return accessToken.IsValid;
} accessToken.IsValid = false;
await _sessionTokens.SaveAsync();
} }
await _sessionTokens.SaveAsync();
return accessToken.IsValid ? accessToken.AccessToken : string.Empty; return accessToken.IsValid;
} }
/// <summary>
/// Checks if a session is valid
/// </summary>
/// <param name="sessionTokenId"></param>
/// <returns></returns>
public bool IsSessionValid(string sessionTokenId)
{
var accessToken = _sessionTokens
.FirstOrDefault(t => t.Id == sessionTokenId);
if (accessToken == null) return false;
if (!accessToken.IsValid) return accessToken.IsValid;
accessToken.Validate();
_sessionTokens.Save();
return accessToken.IsValid;
}
/// <inheritdoc /> /// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)

View File

@ -73,7 +73,7 @@ namespace BlueWest.WebApi
services.AddSession(options => services.AddSession(options =>
{ {
options.Cookie.Domain = "http://localhost:5173"; options.Cookie.Domain = SessionConstants.CookieDomain;
options.Cookie.HttpOnly = true; options.Cookie.HttpOnly = true;
}); });
services services
@ -161,7 +161,8 @@ namespace BlueWest.WebApi
break; break;
default: default:
throw new InvalidOperationException("config.json doesn't specify a valid database. Use mysql or sqlite."); services.PrepareMySqlDatabasePool(_configuration, _environment, configuration);
break;
} }

View File

@ -1,10 +1,11 @@
using System; using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlueWest.Domain;
using BlueWest.Domain;
using BlueWest.Cryptography; using BlueWest.Cryptography;
using BlueWest.WebApi.Context; using BlueWest.WebApi.Configuration;
using BlueWest.WebApi.Context.Users; using BlueWest.WebApi.Context.Users;
using BlueWest.WebApi.EF;
using BlueWest.WebApi.Session; using BlueWest.WebApi.Session;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
@ -152,8 +153,8 @@ namespace BlueWest.WebApi
.AddSingleton(new RedisConnectionProvider(connectionString.Redis)) .AddSingleton(new RedisConnectionProvider(connectionString.Redis))
.AddScoped<IJwtTokenHandler, JwtTokenHandler>() .AddScoped<IJwtTokenHandler, JwtTokenHandler>()
.AddScoped<IJwtFactory, JwtFactory>() .AddScoped<IJwtFactory, JwtFactory>()
.AddHostedService<SessionDataService>() .AddHostedService<SessionManager>()
.AddSingleton<ISessionCache, SessionDataService>() .AddSingleton<ISessionCache, SessionManager>()
.AddScoped<UserRepository>() .AddScoped<UserRepository>()
.AddScoped<IUserManager, ApplicationUserManager>() .AddScoped<IUserManager, ApplicationUserManager>()
.AddScoped<IAuthManager, AuthManager>() .AddScoped<IAuthManager, AuthManager>()
@ -211,7 +212,7 @@ namespace BlueWest.WebApi
{ {
options.Cookie.SameSite = SameSiteMode.Lax; options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.MaxAge = TimeSpan.FromDays(1); options.Cookie.MaxAge = SessionConstants.DefaultSessionMaxAge;
options.LoginPath = "/api/auth/logincookie"; options.LoginPath = "/api/auth/logincookie";
options.LogoutPath = "/api/auth/logout"; options.LogoutPath = "/api/auth/logout";
}) })
@ -231,7 +232,7 @@ namespace BlueWest.WebApi
} }
return Task.CompletedTask; return Task.CompletedTask;
} },
}; };
}); });
@ -272,11 +273,3 @@ namespace BlueWest.WebApi
} }
internal class BlueWestConnectionString
{
public string Redis { get; set; }
public string MySql { get; set; }
}
internal class ConnectionStringDocker : BlueWestConnectionString { }
internal class ConnectionStringNoDocker : BlueWestConnectionString { }

View File

@ -0,0 +1,17 @@
using System.Security.Claims;
using System.Threading.Tasks;
using BlueWest.Data.Application;
namespace BlueWest.WebApi.Context.Users;
public static class AuthConsts
{
/// <summary>
/// Helper object to return a negative callback
/// </summary>
public static (bool, SessionTokenUnique, ClaimsIdentity) NegativeToken => (false, null, null);
public static (bool, SessionTokenUnique, ClaimsIdentity) OkAuth(SessionTokenUnique sessionTokenUnique, ClaimsIdentity claimsIdentity, bool success = true) => (success, sessionTokenUnique, claimsIdentity);
}

View File

@ -3,9 +3,9 @@ using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlueWest.Cryptography; using BlueWest.Cryptography;
using BlueWest.Data.Application; using BlueWest.Data.Application;
using Duende.IdentityServer.Extensions; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using static BlueWest.WebApi.Context.Users.AuthConsts;
namespace BlueWest.WebApi.Context.Users namespace BlueWest.WebApi.Context.Users
{ {
@ -40,8 +40,7 @@ namespace BlueWest.WebApi.Context.Users
var uuid = loginRequest.GetUuid(); var uuid = loginRequest.GetUuid();
var hashUuid = GetHashFromUuid(uuid); var hashUuid = GetHashFromUuid(uuid);
var sessionToken = await _sessionCache.GetSessionTokenByIdAsync(hashUuid); var sessionToken = await _sessionCache.GetSessionTokenByIdAsync(hashUuid);
if (sessionToken != null) return sessionToken; return sessionToken;
return null;
} }
private string GetHashFromUuid(string uuid) private string GetHashFromUuid(string uuid)
@ -51,61 +50,58 @@ namespace BlueWest.WebApi.Context.Users
private SessionToken GetNewSessionToken(LoginRequest loginRequest, ApplicationUser user, string token) private SessionToken GetNewSessionToken(LoginRequest loginRequest, ApplicationUser user, string token)
{ {
long unixTime = ((DateTimeOffset)DateTimeOffset.Now).ToUnixTimeMilliseconds();; long timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var newToken = new SessionToken var newToken = new SessionToken
{ {
Id = GetHashFromUuid(loginRequest.GetUuid()), Id = GetHashFromUuid(loginRequest.GetUuid()),
UserId = user.Id, UserId = user.Id,
CreatedDate = unixTime, CreatedDate = timeNow,
IsValid = true, IsValid = true,
ValidFor = SessionConstants.DefaultSessionMaxAge.Milliseconds,
AccessToken = token AccessToken = token
}; };
return newToken; return newToken;
} }
public bool SessionTokenIsValid(SessionToken token) public bool SessionTokenIsValid(SessionToken token)
{ {
var nowMilliseconds = DateTimeOffset.Now.ToUnixTimeMilliseconds(); var hasChanges = token.Validate();
var isExpired = token.CreatedDate + token.ValidFor > nowMilliseconds; if (hasChanges)
if (isExpired)
{ {
token.IsValid = false;
_sessionCache.SaveAsync(); _sessionCache.SaveAsync();
} }
return token.IsValid; return token.IsValid;
} }
public async Task<(bool, string, ClaimsIdentity)> GetSessionTokenId(LoginRequest loginRequest) public async Task<(bool, SessionTokenUnique, ClaimsIdentity)> GetSessionTokenIdByLoginRequest(LoginRequest loginRequest)
{ {
var user = await _userManager.FindByEmailAsync(loginRequest.Email); var user = await _userManager.FindByEmailAsync(loginRequest.Email);
if (user == null) return (false, string.Empty, null); if (user == null) return NegativeToken;
if (!await _userManager.CheckPasswordAsync(user, loginRequest.Password)) return (false, string.Empty, null); if (!await _userManager.CheckPasswordAsync(user, loginRequest.Password)) return NegativeToken;
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
// Session
var sessionToken = await GetSessionToken(loginRequest); var sessionToken = await GetSessionToken(loginRequest);
if (sessionToken != null) if (sessionToken == null || !SessionTokenIsValid(sessionToken))
{ {
if (SessionTokenIsValid(sessionToken)) var (success, bearerToken) = await GenerateBearerToken(identity, user);
{ var newSessionToken = GetNewSessionToken(loginRequest, user, bearerToken);
return (true, sessionToken.Id, identity); await _sessionCache.AddSessionToken(newSessionToken);
} var tokenUnique = new SessionTokenUnique(newSessionToken);
return OkAuth(tokenUnique, identity, success);
} }
var (success, bearerToken) = await GenerateBearerToken(identity, user); var response = new SessionTokenUnique(sessionToken);
return OkAuth(response, identity);
var newSessionToken = GetNewSessionToken(loginRequest, user, bearerToken);
await _sessionCache.AddSessionToken(newSessionToken);
return (success, newSessionToken.Id, identity);
} }
private async Task<(bool, string)> GenerateBearerToken(ClaimsIdentity identity, ApplicationUser user) private async Task<(bool, string)> GenerateBearerToken(ClaimsIdentity identity, ApplicationUser user)
@ -117,39 +113,18 @@ namespace BlueWest.WebApi.Context.Users
} }
public async Task<(bool, string)> GetBearerTokenBySessionTokenId(string sessionId)
{
if (sessionId.IsNullOrEmpty()) return (false, string.Empty);
var bearer = await _sessionCache.GetBearerByAccessTokenId(sessionId);
return (bearer != string.Empty, bearer);
}
/// <inheritdoc />
public async Task<bool> VerifyLoginByEmailAsync(string email, string password) public async Task<bool> VerifyLoginByEmailAsync(string email, string password)
{ {
var user = await _userManager.FindByEmailAsync(email); var user = await _userManager.FindByEmailAsync(email);
return user != null && await _userManager.CheckPasswordAsync(user, password);
if (user == null)
{
return false;
}
return await _userManager.CheckPasswordAsync(user, password);
} }
private RegisterViewModel FromSignupToUser(RegisterViewModel signupDto)
{
var pwd = signupDto.Password;
var hash = _hasher.CreateHash(pwd, BaseCryptoItem.HashAlgorithm.SHA3_512);
signupDto.Password = hash;
return signupDto;
}
public async Task<IdentityResult> CreateUserAsync(RegisterViewModel userSignupDto) public async Task<IdentityResult> CreateUserAsync(RegisterViewModel userSignupDto)
{ {
RegisterViewModel userToCreate = FromSignupToUser(userSignupDto); userSignupDto.Password = _hasher.CreateHash(userSignupDto.Password, BaseCryptoItem.HashAlgorithm.SHA3_512);;
return await _userManager.CreateAsync(userToCreate.ToUser()); var newUser = userSignupDto.ToUser();
return await _userManager.CreateAsync(newUser);
} }
} }
} }

View File

@ -39,7 +39,7 @@ internal class JwtIssuerOptions
/// <summary> /// <summary>
/// Set the timespan the token will be valid for (default is 120 min) /// Set the timespan the token will be valid for (default is 120 min)
/// </summary> /// </summary>
public TimeSpan ValidFor { get; set; } = SessionConstants.DefaultValidForSpan; public TimeSpan ValidFor { get; set; } = SessionConstants.DefaultSessionMaxAge;
/// <summary> /// <summary>
/// "jti" (JWT ID) Claim (default ID is a GUID) /// "jti" (JWT ID) Claim (default ID is a GUID)

View File

@ -23,13 +23,7 @@ public interface IAuthManager
/// </summary> /// </summary>
/// <param name="loginRequest"></param> /// <param name="loginRequest"></param>
/// <returns></returns> /// <returns></returns>
public Task<(bool, string, ClaimsIdentity)> GetSessionTokenId(LoginRequest loginRequest); public Task<(bool, SessionTokenUnique, ClaimsIdentity)> GetSessionTokenIdByLoginRequest(LoginRequest loginRequest);
/// <summary>
/// Gets a valid bearer token by the session id
/// </summary>
/// <param name="sessionId"></param>
/// <returns></returns>
Task<(bool, string)> GetBearerTokenBySessionTokenId(string sessionId);
} }

View File

@ -2,15 +2,7 @@ namespace BlueWest.WebApi.Context.Users;
public static class Constants public static class Constants
{ {
/// <summary>
/// AdminRoleName
/// </summary>
public const string AdminRoleName = "Admin";
/// <summary>
/// UserRoleName
/// </summary>
public const string UserRoleName = "User";
public const string ExpectatorRoleName = "Expectator";
/// <summary> /// <summary>
/// JwtClaimIdentifiers /// JwtClaimIdentifiers
/// </summary> /// </summary>

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace BlueWest.WebApi.Context.Users;
public class RoleManager : RoleManager<ApplicationRole>
{
public RoleManager(
IRoleStore<ApplicationRole> store,
IEnumerable<IRoleValidator<ApplicationRole>> roleValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
ILogger<RoleManager<ApplicationRole>> logger) : base(store, roleValidators, keyNormalizer, errors, logger)
{
}
}

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlueWest.Domain;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

@ -15,7 +15,6 @@
"MySQL": "server=localhost;user=blueuser;password=dXjw127124dJ;database=bluedb;", "MySQL": "server=localhost;user=blueuser;password=dXjw127124dJ;database=bluedb;",
"Redis": "redis://localhost:6379" "Redis": "redis://localhost:6379"
}, },
"REDIS_CONNECTION_STRING": "redis://redis:6379",
"AuthSettings": { "AuthSettings": {
"SecretKey": "iJWHDmHLpUA283sqsfhqGbMRdRj1PVkH" "SecretKey": "iJWHDmHLpUA283sqsfhqGbMRdRj1PVkH"
}, },

View File

@ -1,5 +1,4 @@
{ {
"mode": "no-docker", "mode": "no-docker",
"database": "sqlite", "database": "mysql"
"environment": "dev"
} }

View File

@ -1,25 +0,0 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*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

View File

@ -1,18 +0,0 @@
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["BlueWest.Console/BlueWest.Console.csproj", "BlueWest.Console/"]
RUN dotnet restore "BlueWest.Console/BlueWest.Console.csproj"
COPY . .
WORKDIR "/src/BlueWest.Console"
RUN dotnet build "BlueWest.Console.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "BlueWest.Console.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "BlueWest.Console.dll"]

View File

@ -1,25 +0,0 @@
using System.Globalization;
/*var s = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (var culture in s)
{
var actualCulture = culture;
Console.WriteLine(actualCulture);
}*/
async Task TaskReceiver(Func<Task> task)
{
Console.WriteLine("Hello");
await task.Invoke();
}
async Task Task2()
{
Console.WriteLine("World");
}
var f = Task2;
await TaskReceiver(f);

View File

@ -11,6 +11,8 @@ namespace BlueWest.WebApi.Context.Users
Token = token; Token = token;
ExpiresIn = expiresIn; ExpiresIn = expiresIn;
} }
void Validate() {}
} }
} }

View File

@ -14,7 +14,7 @@ namespace BlueWest.Data.Application
[IgnoreMemberMapTo] [IgnoreMemberMapTo]
[Indexed] public string Id { get; set; } [Indexed] public string Id { get; set; }
[Indexed] public int ValidFor { get; set;} [Indexed] public long ValidFor { get; set;}
[Indexed] public bool IsValid { get; set; } [Indexed] public bool IsValid { get; set; }
@ -25,6 +25,18 @@ namespace BlueWest.Data.Application
[Indexed] public string AccessToken { get; set; } [Indexed] public string AccessToken { get; set; }
public bool Validate()
{
var expirationDate = CreatedDate + ValidFor;
var timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
if (expirationDate < timeNow)
{
IsValid = false;
return true;
}
return false;
}
} }
} }

View File

@ -7,7 +7,7 @@ namespace BlueWest.Data.Application
{ {
public string Id { get; set; } public string Id { get; set; }
public int ValidFor { get; set;} public long ValidFor { get; set;}
public long CreatedDate { get; set; } public long CreatedDate { get; set; }
public string UserId { get; set; } public string UserId { get; set; }

View File

@ -8,7 +8,7 @@ namespace BlueWest.Data.Geography
public double Latitude { get; set; } public double Latitude { get; set; }
public double Longitude { get; set; } public double Longitude { get; set; }
public Country Country { get; set; } public Country Country { get; set; }
public TimeSpan CreatedTime { get; set; } public long CreatedTime { get; set; }
} }
} }

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BlueWest.Data.Capital\BlueWest.Data.Capital.csproj" />
<ProjectReference Include="..\include\BlueWest.MapTo\src\BlueWest.MapTo\BlueWest.MapTo.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="obj\rider.project.restore.info" />
</ItemGroup>
</Project>

View File

@ -1,11 +1,10 @@
using BlueWest.Data;
using BlueWest.EfMethods; using BlueWest.EfMethods;
using BlueWest.Domain.Model;
using BlueWest.WebApi.Context.Users; using BlueWest.WebApi.Context.Users;
using BlueWest.WebApi.EF.Model;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BlueWest.WebApi.Context namespace BlueWest.Domain
{ {
/// <summary> /// <summary>
/// Application User Db Context /// Application User Db Context

View File

@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<RootNamespace>BlueWest.Domain</RootNamespace>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishDependencyDocumentationFiles>true</PublishDependencyDocumentationFiles>
<AnalysisLevel>preview</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0-pre1" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="6.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authorization.Policy" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.2-mauipre.1.22102.15" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.2-mauipre.1.22054.8" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlueWest.Data.Application\BlueWest.Data.Application.csproj" />
<ProjectReference Include="..\BlueWest.Data.Capital\BlueWest.Data.Capital.csproj" />
<ProjectReference Include="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Content Include="obj\rider.project.restore.info" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Templates\AddToListTemplate.csx" />
<AdditionalFiles Include="Templates\AddToTemplate.csx" />
<AdditionalFiles Include="Templates\GetListTemplate.csx" />
<AdditionalFiles Include="Templates\GetManyTemplate.csx" />
<AdditionalFiles Include="Templates\GetOneByTemplate.csx" />
<AdditionalFiles Include="Templates\GetOneFromListTemplate.csx" />
<AdditionalFiles Include="Templates\GetOneTemplate.csx" />
<AdditionalFiles Include="Templates\UpdateTemplate.csx" />
</ItemGroup>
<Import Project="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.props" />
</Project>

View File

@ -1,10 +1,10 @@
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF.Model; using BlueWest.Domain.Model;
using BlueWest.EfMethods; using BlueWest.EfMethods;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BlueWest.WebApi.EF namespace BlueWest.Domain
{ {
/// <summary> /// <summary>
/// Context for accessing company data /// Context for accessing company data

View File

@ -1,11 +1,11 @@
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF.Model; using BlueWest.Domain.Model;
using BlueWest.EfMethods; using BlueWest.Domain.Extensions;
using BlueWest.WebApi.Context.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using BlueWest.EfMethods;
namespace BlueWest.WebApi.EF namespace BlueWest.Domain
{ {
/// <summary> /// <summary>

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using BlueWest.Data; using BlueWest.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BlueWest.WebApi.Context.Extensions namespace BlueWest.Domain.Extensions
{ {
/// <summary> /// <summary>
/// Code first model builder /// Code first model builder

View File

@ -3,7 +3,7 @@ using BlueWest.Data.Application;
using BlueWest.WebApi.Context.Users; using BlueWest.WebApi.Context.Users;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BlueWest.WebApi.EF.Model namespace BlueWest.Domain.Model
{ {
/// <summary> /// <summary>
/// Database model configuration extensions /// Database model configuration extensions

View File

@ -1,8 +1,8 @@
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.EF.Model; using BlueWest.Domain.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BlueWest.WebApi.EF namespace BlueWest.Domain
{ {
/// <summary> /// <summary>
/// Finance operations table /// Finance operations table

View File

@ -1,10 +1,9 @@
using BlueWest.Data; using BlueWest.Data;
using BlueWest.WebApi.Context.Users; using BlueWest.Domain.Model;
using BlueWest.WebApi.EF.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace BlueWest.WebApi.EF namespace BlueWest.Domain
{ {
/// <summary> /// <summary>

View File

@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -1,8 +0,0 @@
namespace BlueWest.Posts
{
public class PostType
{
}
}

View File

@ -1,11 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<RootNamespace>BlueWest.Views</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,24 @@
using System.Diagnostics;
using BlueWest.Views.Models;
using Microsoft.AspNetCore.Mvc;
namespace BlueWest.Views.Controllers
{
public class CapitalController : Controller
{
public IActionResult Index()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
}
}
}

View File

@ -0,0 +1,31 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using BlueWest.Views.Models;
namespace BlueWest.Views.Controllers;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
namespace BlueWest.Views.Controllers
{
public class SystemController : Controller
{
private readonly ILogger<SystemController> _logger;
public SystemController(ILogger<SystemController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Users()
{
return View();
}
public IActionResult Roles()
{
return View();
}
}
}

20
BlueWest.Views/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["BlueWest.Views/BlueWest.Views.csproj", "BlueWest.Views/"]
RUN dotnet restore "BlueWest.Views/BlueWest.Views.csproj"
COPY . .
WORKDIR "/src/BlueWest.Views"
RUN dotnet build "BlueWest.Views.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "BlueWest.Views.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "BlueWest.Views.dll"]

View File

@ -0,0 +1,8 @@
namespace BlueWest.Views.Models;
public class ErrorViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

View File

@ -0,0 +1,8 @@
namespace BlueWest.Views.Models
{
public class SystemPageModel
{
}
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlueWest.Views.Models
{
public class UserPageModel : PageModel
{
}
}

27
BlueWest.Views/Program.cs Normal file
View File

@ -0,0 +1,27 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// 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.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

View File

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51856",
"sslPort": 44359
}
},
"profiles": {
"BlueWest.Views": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7022;http://localhost:5204",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Capital</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -0,0 +1 @@
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">BlueWest.Views</a>

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - BlueWest.Views</title>
<link rel="stylesheet" href="~/static/main.css"/>
<link rel="stylesheet" href="~/BlueWest.Views.styles.css" asp-append-version="true"/>
<style>
@@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2") format('woff2'), url('/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff') format('woff');
}
</style>
</head>
<body class="desktop touch body--dark" style="--q-primary:#414141;">
<div id="q-app">
<div class="wrap s-7IPF32Wcq3s8"><ul class="_toastContainer s-lp0GEKiD9Hz6">
</ul></div>
<div class="q-layout q-layout--standard s-7IPF32Wcq3s8" id="app-entry"><header class="q-header q-layout__section--marginal fixed-top bg-primary text-white s-7IPF32Wcq3s8"><div class="q-tabs row no-wrap items-center q-tabs--not-scrollable q-tabs--horizontal q-tabs__arrows--inside s-7IPF32Wcq3s8"><div class="q-tabs__content row no-wrap items-center self-stretch hide-scrollbar relative-position q-tabs__content--align-left s-7IPF32Wcq3s8"><div></div>
<a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-focusable q-hoverable cursor-pointer " href="/system"><div class="q-focus-helper"></div>
<div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"><div class="q-tab__label">System</div></div>
<div class="q-tab__indicator absolute-bottom"></div></a><div></div>
<a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-focusable q-hoverable cursor-pointer " href="/capital"><div class="q-focus-helper"></div>
<div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"><div class="q-tab__label">Capital</div></div>
<div class="q-tab__indicator absolute-bottom"></div></a></div>
</div>
<div class="q-layout__shadow absolute-full overflow-hidden no-pointer-events s-7IPF32Wcq3s8"></div></header>
<div class="q-drawer-container s-7IPF32Wcq3s8"><div class="q-drawer__opener fixed-right s-7IPF32Wcq3s8"></div>
<div class="fullscreen q-drawer__backdrop hidden s-7IPF32Wcq3s8" style="background-color: rgba(0, 0, 0, 0);"></div>
<aside class="q-drawer q-drawer--right q-drawer--bordered q-drawer--dark q-dark q-layout--prevent-focus fixed q-drawer--on-top q-drawer--mobile q-drawer--top-padding s-7IPF32Wcq3s8" style="width: 300px; transform: translateX(300px);"><div show-if="" class="q-drawer__content fit scroll s-7IPF32Wcq3s8"></div></aside></div>
<div class="q-page-container s-7IPF32Wcq3s8" style="padding-top: 48px; padding-bottom: 49px;">
@RenderBody()
<footer class="q-footer q-layout__section--marginal absolute-bottom q-footer--bordered text-secondary s-7IPF32Wcq3s8">
<div class="q-tabs row no-wrap items-center q-tabs--not-scrollable q-tabs--horizontal q-tabs__arrows--inside text-white s-7IPF32Wcq3s8">
<div class="q-tabs__content row no-wrap items-center self-stretch hide-scrollbar relative-position q-tabs__content--align-center s-7IPF32Wcq3s8">
<div style="margin-right: 1rem;" class="s-7IPF32Wcq3s8"><a href="#/geo" class="router-link-active router-link-exact-active s-7IPF32Wcq3s8"><i class="q-icon text-white notranslate material-icons s-7IPF32Wcq3s8" style="font-size: 24px;">data_usage</i></a></div>
<a href="/system" class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--no-caps q-focusable q-hoverable cursor-pointer">
<div class="q-focus-helper"></div>
<div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column">
<div class="q-tab__label">System</div></div>
<div class="q-tab__indicator absolute-bottom text-transparent"></div></a>
<a href="/system/users" class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--no-caps q-focusable q-hoverable cursor-pointer">
<div class="q-focus-helper"></div>
<div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column">
<div class="q-tab__label">Users</div></div>
<div class="q-tab__indicator absolute-bottom text-transparent"></div></a>
<a href="/system/roles" class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--no-caps q-focusable q-hoverable cursor-pointer">
<div class="q-focus-helper"></div>
<div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column">
<div class="q-tab__label">Roles</div></div>
<div class="q-tab__indicator absolute-bottom text-transparent"></div></a>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-btn--dense s-7IPF32Wcq3s8" type="button"><span class="q-focus-helper s-7IPF32Wcq3s8"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row s-7IPF32Wcq3s8"><i class="q-icon notranslate material-icons s-7IPF32Wcq3s8">menu</i></span></button></div>
</div></footer>
</div>
</div>
</div>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,24 @@

.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}

View File

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">System</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Roles</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Users</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

View File

@ -0,0 +1,3 @@
@using BlueWest.Views
@using BlueWest.Views.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,18 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,4 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,427 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

Some files were not shown because too many files have changed in this diff Show More