Api controller using cookie auth

This commit is contained in:
code liturgy 2022-11-16 21:17:44 +00:00
parent 05925a1d96
commit a45c212592
39 changed files with 424 additions and 473 deletions

6
.gitmodules vendored
View File

@ -4,9 +4,9 @@
[submodule "include/Math-Expression-Evaluator"] [submodule "include/Math-Expression-Evaluator"]
path = include/Math-Expression-Evaluator path = include/Math-Expression-Evaluator
url = git@git.codeliturgy.com:P0/Math-Expression-Evaluator.git url = git@git.codeliturgy.com:P0/Math-Expression-Evaluator.git
[submodule "BlueWest.Frontend"] [submodule "CodeLiturgy.Dashboard.Frontend"]
path = BlueWest.Frontend path = CodeLiturgy.Dashboard.Frontend
url = git@git.codeliturgy.com:P0/BlueWest.Frontend.git url = git@git.codeliturgy.com:P0/CodeLiturgy.Dashboard.Frontend.git
[submodule "include/BlueWest.EfMethods"] [submodule "include/BlueWest.EfMethods"]
path = include/BlueWest.EfMethods path = include/BlueWest.EfMethods
url = git@git.codeliturgy.com:P0/BlueWest.EfGenerator.git url = git@git.codeliturgy.com:P0/BlueWest.EfGenerator.git

@ -1 +0,0 @@
Subproject commit 8b2e541cfe92c7bfaada6a95e427a3d6c92ce768

@ -0,0 +1 @@
Subproject commit d8e4df2c5d247b02accbbd5c668c4427145806d3

View File

@ -12,7 +12,8 @@ namespace BlueWest.Data.Auth
/// <summary> /// <summary>
/// API User policy Key /// API User policy Key
/// </summary> /// </summary>
public const string ApiNamePolicy = "ApiUser"; public const string ApiNamePolicy = "Bearer";
public const string CookieNamePolicy = "Cookies";
public const string SessionTokenHeaderName = "x-bw2-auth"; public const string SessionTokenHeaderName = "x-bw2-auth";
public const string CookieDomain = "https://localhost:7022"; public const string CookieDomain = "https://localhost:7022";

View File

@ -3,6 +3,8 @@ namespace BlueWest.Data.Auth.Context.Users;
public static class Constants public static class Constants
{ {
public const string CorsPolicyName = "_myAllowSpecificOrigins";
/// <summary> /// <summary>
/// JwtClaimIdentifiers /// JwtClaimIdentifiers
/// </summary> /// </summary>
@ -21,4 +23,14 @@ public static class Constants
/// </summary> /// </summary>
public const string ApiAccess = "api_access"; public const string ApiAccess = "api_access";
} }
public static class CookieClaims
{
/// <summary>
/// JwtClaims.ApiAccess
/// </summary>
public const string CookieAccess = "cookie_access";
}
} }

View File

@ -0,0 +1,8 @@
namespace CodeLiturgy.Data.Capital;
public enum EnvironmentType
{
Development,
Staging,
Production
}

View File

@ -0,0 +1,30 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using MapTo;
namespace CodeLiturgy.Data.Capital
{
[MapFrom(new []
{
typeof(SiteUnique),
typeof(SiteCreate)
})]
public partial class Site
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string Name { get; set; }
public string Domain { get; set; }
public string Ip { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime LastChanged { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using MapTo;
namespace CodeLiturgy.Data.Capital
{
[MapFrom(typeof(Site))]
public partial class SiteCreate
{
public string Id { get; set; }
public string Name { get; set; }
public string Domain { get; set; }
public string Ip { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using MapTo;
namespace CodeLiturgy.Data.Capital
{
[MapFrom(typeof(Site))]
public partial class SiteUnique
{
public string Id { get; set; }
public string Name { get; set; }
public string Domain { get; set; }
public string Ip { get; set; }
}
}

View File

@ -53,7 +53,6 @@ namespace CodeLiturgy.Domain
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);
builder.ConfigureCurrentDbModel(); builder.ConfigureCurrentDbModel();

View File

@ -0,0 +1,26 @@
using CodeLiturgy.Data.Capital;
using Microsoft.EntityFrameworkCore;
using BlueWest.EfMethods;
namespace CodeLiturgy.Domain;
public class SiteDbContext: DbContext
{
[EfGetOneBy(nameof(Site.Id), typeof(SiteUnique))]
[EfGetOne(typeof(SiteUnique))]
[EfAddMethods(createType: typeof(SiteCreate), returnType: typeof(SiteUnique))]
[EfGetMany(typeof(SiteUnique))]
public DbSet<Site> Sites { get; set; }
/// <summary>
/// SiteDbContext Constructor.
/// </summary>
/// <param name="options"></param>
public SiteDbContext(DbContextOptions<SiteDbContext> options) : base(options)
{
Database.EnsureCreated();
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup> </PropertyGroup>
@ -79,10 +79,10 @@
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map" /> <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt" /> <_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt" />
<_ContentIncludedByDefault Remove="Views\Data\Roles\Index.cshtml" /> <_ContentIncludedByDefault Remove="Views\Data\Roles\Index.cshtml" />
</ItemGroup> <_ContentIncludedByDefault Remove="Views\Account\ChangePassword.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\Curriculums\Index.cshtml" />
<ItemGroup> <_ContentIncludedByDefault Remove="Views\Account\Curriculums\Upload.cshtml" />
<AdditionalFiles Include="Views\Account\Index.cshtml" /> <_ContentIncludedByDefault Remove="Views\Account\Index.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -109,4 +109,9 @@
<Folder Include="wwwroot\static\profile" /> <Folder Include="wwwroot\static\profile" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Views\Auth\Account.cshtml" />
<AdditionalFiles Include="Views\EnvironmentsPage\Index.cshtml" />
</ItemGroup>
</Project> </Project>

View File

@ -12,7 +12,6 @@ public class AccountController : UserController
{ {
private readonly ILogger<AccountController> _logger; private readonly ILogger<AccountController> _logger;
public AccountController(ApplicationUserManager userManager, ILogger<AccountController> logger) : base(userManager, logger) public AccountController(ApplicationUserManager userManager, ILogger<AccountController> logger) : base(userManager, logger)
{ {
_userManager = userManager; _userManager = userManager;
@ -27,19 +26,9 @@ public class AccountController : UserController
public override void OnInitialization() public override void OnInitialization()
{ {
SetFooterMenu(LayoutCache.AccountRouteRecord.Children); SetFooterMenu(LayoutCache.AccountRouteRecord.ChildrenToUrl());
} }
public async Task<IActionResult> Curriculums()
{
await OnEveryAction();
return View("Curriculums/Index");
}
public async Task<IActionResult> Change() public async Task<IActionResult> Change()
{ {
await OnEveryAction(); await OnEveryAction();
@ -47,14 +36,6 @@ public class AccountController : UserController
} }
[Microsoft.AspNetCore.Mvc.Route("[controller]/upload")]
public async Task<IActionResult> UploadCurriculum()
{
await OnEveryAction();
return View("Curriculums/Upload");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Error() public async Task<IActionResult> Error()
{ {

View File

@ -62,6 +62,13 @@ namespace CodeLiturgy.Views.Controllers
return View(); return View();
} }
public async Task<IActionResult> Account()
{
await OnEveryAction();
this.HandleGlobalization();
return View();
}
public async Task<IActionResult> Logout() public async Task<IActionResult> Logout()
{ {

View File

@ -0,0 +1,39 @@
using BlueWest.Data.Auth.Context.Users;
using CodeLiturgy.Data.Capital;
using CodeLiturgy.Domain;
using CodeLiturgy.Views.Utils;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace CodeLiturgy.Views.Controllers
{
[Route("api/envs")]
[ApiController]
[Authorize]
public class EnvironmentsController : ControllerBase
{
private ApplicationUserManager _userManager;
private ILogger<UserController> _logger;
private SiteDbContext _siteDbContext;
public EnvironmentsController(ApplicationUserManager userManager, ILogger<UserController> logger, SiteDbContext siteDbContext)
{
_logger = logger;
_userManager = userManager;
_siteDbContext = siteDbContext;
}
[HttpGet]
public async Task<ActionResult<Site>> GetSites()
{
return Ok(new Site());
}
}
}

View File

@ -0,0 +1,34 @@
using BlueWest.Data.Auth.Context.Users;
using CodeLiturgy.Views.Utils;
using Duende.IdentityServer.Extensions;
using Microsoft.AspNetCore.Mvc;
namespace CodeLiturgy.Views.Controllers;
public class EnvironmentsPageController : UserController
{
public EnvironmentsPageController(ApplicationUserManager userManager, ILogger<UserController> logger) : base(userManager, logger) { }
public override void OnInitialization()
{
base.OnInitialization();
SetHeaderMenu(new List<Url>()
{
});
}
public async Task<IActionResult> Index()
{
await OnEveryAction();
if (!User.IsAuthenticated())
{
return Redirect("/auth/login");
}
return View();
}
}

View File

@ -18,6 +18,12 @@ public class HomeController : UserController
_logger = logger; _logger = logger;
} }
public override void OnInitialization()
{
var menu = LayoutCache.Root.ChildrenToUrl();
SetHeaderMenu(menu);
}
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> Index() public async Task<IActionResult> Index()

View File

@ -21,7 +21,7 @@ namespace CodeLiturgy.Views.Controllers
public override void OnInitialization() public override void OnInitialization()
{ {
SetFooterMenu(LayoutCache.SystemRoute.Children); SetFooterMenu(LayoutCache.SystemRoute.ChildrenToUrl());
} }
public async Task<IActionResult> Users() public async Task<IActionResult> Users()

View File

@ -15,22 +15,24 @@ public class UserController : Controller
protected ILogger<UserController> _logger; protected ILogger<UserController> _logger;
protected ApplicationUserManager _userManager; protected ApplicationUserManager _userManager;
private List<RouteRecord> _footerMenu; private List<Url> _footerMenu;
private List<Url> _headerMenu;
public UserController(ApplicationUserManager userManager, ILogger<UserController> logger) public UserController(ApplicationUserManager userManager, ILogger<UserController> logger)
{ {
_logger = logger; _logger = logger;
_userManager = userManager; _userManager = userManager;
_footerMenu = new List<RouteRecord>(); _footerMenu = new List<Url>();
_headerMenu = new List<Url>();
} }
public async Task OnEveryAction() public async Task OnEveryAction()
{ {
HandleGlobalization(); HandleGlobalization();
SetFooterMenuViewData();
await SetUserProfileViewData(); await SetUserProfileViewData();
OnInitialization(); OnInitialization();
SetFooterMenuViewData(); SetFooterMenuViewData();
SetHeaderMenuViewData();
} }
public virtual void OnInitialization() public virtual void OnInitialization()
@ -42,9 +44,21 @@ public class UserController : Controller
ViewData[FooterMenuViewDataId] = _footerMenu; ViewData[FooterMenuViewDataId] = _footerMenu;
} }
public void SetFooterMenu(List<RouteRecord> routeRecords) protected void SetHeaderMenuViewData()
{ {
_footerMenu = routeRecords; ViewData[HeaderMenuId] = _headerMenu;
}
public void SetFooterMenu(List<Url> urls)
{
_footerMenu = urls;
}
public void SetHeaderMenu(List<Url> urls)
{
_headerMenu = urls;
} }
public async Task SetUserProfileViewData() public async Task SetUserProfileViewData()

View File

@ -1,6 +1,6 @@
namespace CodeLiturgy.Views.Languages; namespace CodeLiturgy.Views.Languages;
public static class SiteContent public static class Translations
{ {
internal static readonly Dictionary<string, Dictionary<string, string>> RouteTitle = internal static readonly Dictionary<string, Dictionary<string, string>> RouteTitle =
new Dictionary<string, Dictionary<string, string>> new Dictionary<string, Dictionary<string, string>>
@ -21,14 +21,6 @@ public static class SiteContent
{"en-gb", "System"}, {"en-gb", "System"},
} }
}, },
{
DataKeyName, new Dictionary<string, string>()
{
{"pt", "Dados"},
{"eng", "Data"},
{"en-gb", "Data"}
}
},
{ {
RolesKeyName, new Dictionary<string, string>() RolesKeyName, new Dictionary<string, string>()
@ -62,68 +54,23 @@ public static class SiteContent
{"en-gb", "Settings"}, {"en-gb", "Settings"},
} }
}, },
{
CompaniesKeyName, new Dictionary<string, string>()
{
{"pt", "Empresas"},
{"eng", "Companies"},
{"en-gb", "Companies"},
}
},
{ {
IndustriesKeyName, new Dictionary<string, string>() AuthAccountKeyName, new Dictionary<string, string>()
{ {
{"pt", "Indústrias"}, {"pt", "Conta"},
{"en-gb", "Industries"},
{"eng", "Industries"},
}
},
{
CurrenciesKeyName, new Dictionary<string, string>()
{
{"pt", "Moedas"},
{"en-gb", "Currencies"},
{"eng", "Currencies"},
}
},
{
CountriesKeyName, new Dictionary<string, string>()
{
{"pt", "Países"},
{"eng", "Countries"},
{"en-gb", "Countries"}
}
},
{
BanksKeyName, new Dictionary<string, string>()
{
{"pt", "Bancos"},
{"eng", "Banks"},
{"en-gb", "Banks"},
}
},
{
DataUsersKeyName, new Dictionary<string, string>()
{
{"pt", "Utilizadores"},
{"eng", "Users"},
{"en-gb", "Users"},
}
},
{
AccountKeyName, new Dictionary<string, string>()
{
{"pt", "Perfil"},
{"eng", "Account"}, {"eng", "Account"},
{"en-gb", "Account"}, {"en-gb", "Account"},
} }
}, },
{
EnvironmentsKeyName, new Dictionary<string, string>()
{
{"pt", "Ambientes"},
{"eng", "Environments"},
{"en-gb", "Environments"},
}
},
{ {
ChangePasswordKeyName, new Dictionary<string, string>() ChangePasswordKeyName, new Dictionary<string, string>()

View File

@ -112,6 +112,24 @@ public static class StartupExtensions
{ {
options.LoginPath = Routes.AuthLoginRoute; options.LoginPath = Routes.AuthLoginRoute;
options.LogoutPath = Routes.AuthLogoutRoute; options.LogoutPath = Routes.AuthLogoutRoute;
})
.AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
configureOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
},
};
}); });
@ -121,6 +139,10 @@ public static class StartupExtensions
options.AddPolicy(SessionConstants.ApiNamePolicy, options.AddPolicy(SessionConstants.ApiNamePolicy,
policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol, policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol,
Constants.JwtClaims.ApiAccess)); Constants.JwtClaims.ApiAccess));
options.AddPolicy(SessionConstants.CookieNamePolicy, policy =>
{
policy.RequireClaim(Constants.CookieClaims.CookieAccess);
});
}); });
// add identity // add identity
@ -168,6 +190,7 @@ public static class StartupExtensions
{ {
return serviceCollection return serviceCollection
.AddDbContextPool<UserDbContext>(options => options.UsePsqlConfiguration(configuration)) .AddDbContextPool<UserDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<SiteDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<CountryDbContext>(options => options.UsePsqlConfiguration(configuration)) .AddDbContextPool<CountryDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<ApplicationUserDbContext>(options => .AddDbContextPool<ApplicationUserDbContext>(options =>
options.UsePsqlConfiguration(configuration)); options.UsePsqlConfiguration(configuration));

View File

@ -10,16 +10,22 @@ internal class LayoutCache
#region Route Tree #region Route Tree
private static readonly RouteRecord NonLoggedInRoot = new RouteRecord( private static readonly RouteRecord NonLoggedInRoot = new RouteRecord(
"Login",
AuthLoginRoute, AuthLoginRoute,
AuthLoginKeyName, AuthLoginKeyName,
nameof(AuthController), new List<RouteRecord>()); nameof(AuthController), new List<RouteRecord>());
private static readonly RouteRecord Root = new RouteRecord( internal static readonly RouteRecord Root = new RouteRecord(
"Code Liturgy - Dashboard",
RootKeyName, RootKeyName,
RootLocation, RootLocation,
nameof(HomeController), nameof(HomeController),
new List<RouteRecord>(), ViewType.Root); new List<RouteRecord>()
{
new RouteRecord("Environments", EnvironmentsKeyName, EnvironmentsRouteLocation, nameof(EnvironmentsPageController), new List<RouteRecord>(), ViewType.Environments)
}, ViewType.Root);
#endregion Route Tree #endregion Route Tree
@ -28,9 +34,6 @@ internal class LayoutCache
internal static readonly RouteRecord SystemRoute = internal static readonly RouteRecord SystemRoute =
Root.Children.FirstOrDefault(x => x.ViewType == ViewType.System)!; Root.Children.FirstOrDefault(x => x.ViewType == ViewType.System)!;
internal static readonly RouteRecord DataRouteRecord =
Root.Children.FirstOrDefault(x => x.ViewType == ViewType.Data)!;
internal static readonly RouteRecord AccountRouteRecord = internal static readonly RouteRecord AccountRouteRecord =
Root.Children.FirstOrDefault(x => x.ViewType == ViewType.Account)!; Root.Children.FirstOrDefault(x => x.ViewType == ViewType.Account)!;
@ -38,72 +41,50 @@ internal class LayoutCache
#region Internal Menus #region Internal Menus
internal static List<RouteView> GetDefaultFooterMenu(ViewDataDictionary dictionary)
internal static List<Url> GetDefaultHeaderMenu(ViewDataDictionary dictionary, bool userAuthenticated = false)
{ {
var location = GetUserLanguage(dictionary); var menuToShow = new List<Url>();
var menu = LayoutCache menuToShow.Add(Root.ToUrl());
.Root
.Children;
if (dictionary[FooterMenuViewDataId] is List<RouteRecord> footerMenu)
{
menu = footerMenu;
}
return menu
.Select(x =>
{
if (SiteContent.RouteTitle[x.routeKey].ContainsKey(location))
{
return new RouteView(SiteContent.RouteTitle[x.routeKey][location],
location);
}
return new RouteView(SiteContent.RouteTitle[x.routeKey][DefaultCultureName],
x.location);
})
.ToList();
}
internal static List<RouteView> GetDefaultHeaderMenu(ViewDataDictionary dictionary, bool userAuthenticated = false)
{
if (!userAuthenticated) if (!userAuthenticated)
{ {
var menuToShow = new List<RouteView>();
menuToShow.Add(new RouteView("CodeLiturgy Dashboard", "/"));
return menuToShow; return menuToShow;
} }
var location = GetUserLanguage(dictionary); if (dictionary[HeaderMenuId] is List<Url> headerMenu)
var menu = LayoutCache
.Root
.Children;
if (dictionary[HeaderMenuId] is List<RouteRecord> footerMenu)
{ {
menu = footerMenu; menuToShow = menuToShow
} .Concat(headerMenu)
return menu
.Select(x =>
{
if (SiteContent.RouteTitle[x.routeKey].ContainsKey(location))
{
return new RouteView(SiteContent.RouteTitle[x.routeKey][location],
x.Location);
}
return new RouteView(SiteContent.RouteTitle[x.routeKey][DefaultCultureName],
x.location);
})
.ToList(); .ToList();
} }
return menuToShow;
}
internal static List<Url> GetDefaultFooterMenu(ViewDataDictionary dictionary, bool userAuthenticated)
{
var menuToShow = new List<Url>();
menuToShow.Add(Root.ToUrl());
if (!userAuthenticated)
{
return menuToShow;
}
if (dictionary[FooterMenuViewDataId] is List<Url> footerMenu)
{
menuToShow = menuToShow
.Concat(footerMenu)
.ToList();
}
return menuToShow;
}
[Conditional("DEBUG")] [Conditional("DEBUG")]
internal static void IsDevMode(ref bool isDebug) internal static void IsDevMode(ref bool isDebug)

View File

@ -1,14 +1,33 @@
namespace CodeLiturgy.Views.Utils namespace CodeLiturgy.Views.Utils
{ {
public record RouteRecord(string routeKey, string location, string controllerName, List<RouteRecord> children, ViewType viewType = ViewType.Undefined) public class Url
{ {
public string RouteKey = routeKey; public string Name;
public string Location = location; public string Location;
public string ControllerName;
public List<RouteRecord> Children = children; public Url(string name, string location)
public ViewType ViewType = viewType; {
Name = name;
Location = location;
}
}
public record RouteRecord(string Name, string RouteKey, string Location, string ControllerName, List<RouteRecord> Children, ViewType ViewType = ViewType.Undefined)
{
public List<Url> ChildrenToUrl()
{
return Children.Select(x => x.ToUrl()).ToList();
}
public Url ToUrl()
{
return new Url(Name, Location);
}
public Url ToUrl(string name)
{
return new Url(name, Location);
}
} }
public record RouteView(string Name, string Location);
} }

View File

@ -46,47 +46,22 @@ namespace CodeLiturgy.Views.Utils
internal const string SettingsRouteLocation = $"{SystemRouteLocation}/settings"; internal const string SettingsRouteLocation = $"{SystemRouteLocation}/settings";
internal const string SettingsKeyName = "settings"; internal const string SettingsKeyName = "settings";
internal const string DataLocation = $"/data";
internal const string DataKeyName = "data";
internal const string DataUsersLocation = "/data/users";
internal const string DataUsersKeyName = "users";
internal const string SystemRouteLocation = $"/system"; internal const string SystemRouteLocation = $"/system";
internal const string SystemKeyName = "system"; internal const string SystemKeyName = "system";
internal const string BanksKeyName = "banks";
internal const string BanksLocation = $"{DataLocation}/banks";
internal const string CountriesLocation = $"{DataLocation}/countries";
internal const string CountriesKeyName = "countries";
internal const string CurrenciesLocation = $"{DataLocation}/currencies";
internal const string CurrenciesKeyName = "currencies";
internal const string IndustriesKeyName = "industries";
internal const string IndustriesLocation = $"{DataLocation}/industries";
internal const string CompaniesLocation = $"{DataLocation}/companies";
internal const string CompaniesKeyName = "companies";
// Jobs
internal const string Curriculums = "curriculums";
internal const string Proposals = "proposals";
internal const string Offers = "offers";
internal const string Processes = "processes";
internal const string AuthLogoutRoute = "/auth/logout"; internal const string AuthLogoutRoute = "/auth/logout";
internal const string AuthAccountRoute = "/auth/account";
internal const string AuthAccountKeyName = "account";
// Account // Account
internal const string AccountRouteLocation = "/account";
internal const string AccountKeyName = "profile";
internal const string ChangePasswordKeyName = "changepwd"; internal const string ChangePasswordKeyName = "changepwd";
internal const string ChangePasswordRouteLocation = $"{AccountRouteLocation}/change"; internal const string ChangePasswordRouteLocation = $"{AuthAccountRoute}/change";
// Environments
internal const string EnvironmentsRouteLocation = "/environments";
internal const string EnvironmentsKeyName = "envs";
} }
} }

View File

@ -5,8 +5,8 @@ namespace CodeLiturgy.Views
public enum ViewType public enum ViewType
{ {
System, System,
Data,
Account, Account,
Environments,
Root, Root,
Undefined Undefined

View File

@ -1,111 +0,0 @@
@using CodeLiturgy.Views.Utils
@model BlueWest.Data.Application.Users.ApplicationUserUnique
@{
var user = @Model;
}
<div class="q-ma-lg">
<form method="POST" action="EditProfile" class="q-form q-gutter-md">
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.Id" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Id</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" name="email" aria-label="Email" type="email" value="@user.Email">
</div>
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Email address</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.PhoneNumber">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Phone Number</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.NormalizedEmail" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Normalized Email</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.UserName">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Username</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Password Hash" name="name" type="name" value="@Model.PasswordHash" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Username</div>
</div>
</div>
</div>
</div>
</label>
<div>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-primary text-white q-btn--actionable q-focusable q-hoverable">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Save</span>
</span>
</button>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle text-primary q-btn--actionable q-focusable q-hoverable q-ml-sm" type="reset">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Reset</span>
</span>
</button>
</div>
</form>
</div>
@await Html.PartialAsync("_FooterMenu")

View File

@ -1,10 +0,0 @@
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Curriculums Module</h1>
</div>
@Html.Partial("_FooterMenu");

View File

@ -1,111 +0,0 @@
@using CodeLiturgy.Views.Utils
@model BlueWest.Data.Application.Users.ApplicationUserUnique
@{
var user = @Model;
}
<div class="q-ma-lg">
<form method="POST" action="EditProfile" class="q-form q-gutter-md">
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.Id" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Id</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" name="email" aria-label="Email" type="email" value="@user.Email">
</div>
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Email address</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.PhoneNumber">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Phone Number</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.NormalizedEmail" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Normalized Email</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Phone Number" name="name" type="name" value="@Model.UserName">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Username</div>
</div>
</div>
</div>
</div>
</label>
<label class="q-field row no-wrap items-start q-field--filled q-input q-field--labeled q-field--dark q-field--with-bottom">
<div class="q-field__inner relative-position col self-stretch">
<div class="q-field__control relative-position row no-wrap" tabindex="-1">
<div class="q-field__control-container col relative-position row no-wrap q-anchor--skip">
<input class="q-field__native q-placeholder" tabindex="0" aria-label="Password Hash" name="name" type="name" value="@Model.PasswordHash" disabled="disabled">
</div>
<div class="q-field__bottom row items-start q-field__bottom--animated">
<div class="q-field__messages col">
<div>Username</div>
</div>
</div>
</div>
</div>
</label>
<div>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-primary text-white q-btn--actionable q-focusable q-hoverable">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Save</span>
</span>
</button>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle text-primary q-btn--actionable q-focusable q-hoverable q-ml-sm" type="reset">
<span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Reset</span>
</span>
</button>
</div>
</form>
</div>
@await Html.PartialAsync("_FooterMenu")

View File

@ -0,0 +1 @@
<h1>Account</h1>

View File

@ -1,5 +0,0 @@
@model BlueWest.Data.Auth.Context.Users.LoginRequest
@using (Html.BeginForm()){
@Html.LabelFor(x => x.Password, "Password")
@Html.PasswordFor(x => x.Password);
}

View File

@ -101,7 +101,7 @@
<button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-primary text-white q-btn--actionable q-focusable q-hoverable"> <button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-primary text-white q-btn--actionable q-focusable q-hoverable">
<span class="q-focus-helper"></span> <span class="q-focus-helper"></span>
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row"> <span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Login</span> <span class="block">Signup</span>
</span> </span>
</button> </button>
<button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle text-primary q-btn--actionable q-focusable q-hoverable q-ml-sm" type="reset"> <button class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--rectangle text-primary q-btn--actionable q-focusable q-hoverable q-ml-sm" type="reset">

View File

@ -1,18 +0,0 @@
@model dynamic
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
<div>
</div>
</body>
</html>

View File

@ -0,0 +1,5 @@
<div class="text-center">
</div>

View File

@ -1,7 +1,8 @@
@using CodeLiturgy.Views.Utils @using CodeLiturgy.Views.Utils
@using Duende.IdentityServer.Extensions
@{ @{
Layout = null; Layout = null;
var menu = LayoutCache.GetDefaultFooterMenu(ViewData); var menu = LayoutCache.GetDefaultFooterMenu(ViewData, User.IsAuthenticated());
} }

View File

@ -4,10 +4,12 @@
@{ @{
Layout = null; Layout = null;
var userAuthenticated = User.IsAuthenticated(); var userAuthenticated = User.IsAuthenticated();
var menu = LayoutCache.GetDefaultHeaderMenu(ViewData, userAuthenticated);
var user = ViewData.GetUserViewData(); var user = ViewData.GetUserViewData();
var rootUrl = SessionConstants.CookieDomain; var rootUrl = SessionConstants.CookieDomain;
var menu = LayoutCache.GetDefaultHeaderMenu(ViewData, User.IsAuthenticated());
} }
<div class="q-tabs items-center row no-wrap items-center q-tabs--not-scrollable q-tabs--horizontal q-tabs__arrows--inside"> <div class="q-tabs items-center row no-wrap items-center q-tabs--not-scrollable q-tabs--horizontal q-tabs__arrows--inside">
@ -26,7 +28,7 @@
} }
@if (userAuthenticated && user != null) @if (userAuthenticated && user != null)
{ {
<div class="q-chip row inline no-wrap items-center q-chip--dark q-dark"> <div class="q-chip row inline no-wrap items-center q-chip--dark q-dark" id="profile-wrap">
<div class="q-chip__content col row no-wrap items-center q-anchor--skip"> <div class="q-chip__content col row no-wrap items-center q-anchor--skip">
<div class="q-avatar"> <div class="q-avatar">
<div class="q-avatar__content row flex-center overflow-hidden"> <div class="q-avatar__content row flex-center overflow-hidden">
@ -35,8 +37,46 @@
</div> @user.Email </div> @user.Email
</div> </div>
</div> </div>
<style lang="css">
</style>
} }
</div>
</div>
@if (userAuthenticated && user != null)
{
<div class="profileMenuContainer" id="profile-menu" style="display: none">
<div>
<a href="@AuthAccountRoute" class="profile-link">
<button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-black text-white q-btn--actionable q-focusable q-hoverable q-btn--active" type="button">
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Profile</span>
</span>
</button>
</a>
</div>
<div>
<a href="@AuthLogoutRoute" class="profile-link">
<button class="q-btn q-btn-item non-selectable no-outline q-btn--standard q-btn--rectangle bg-black text-white q-btn--actionable q-focusable q-hoverable q-btn--active" type="button">
<span class="q-btn__content text-center col items-center q-anchor--skip justify-center row">
<span class="block">Logout</span>
</span>
</button>
</a>
</div> </div>
</div> </div>
}
<script >
(() => {
const wrapMenu = document.getElementById("profile-wrap");
const profileMenu = document.getElementById("profile-menu");
wrapMenu.onclick = () => {
if (profileMenu.style.display === "none") {
profileMenu.style.display = "block";
} else {
profileMenu.style.display = "none";
}
}
})();
</script>

View File

@ -10,10 +10,9 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - CodeLiturgy.Views</title> <title>@ViewData["Title"] - CodeLiturgy.Views</title>
<link rel="stylesheet" href="@rootUrl/static/main.css"/> <link rel="stylesheet" href="@rootUrl/static/main.css" asp-append-version="true"/>
@* <link rel="stylesheet" href="@rootUrl/static/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/CodeLiturgy.Views.styles.css" asp-append-version="true"/>
*@
<link rel="preload" href="@rootUrl/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff" as="font" type="font/woff" crossorigin> <link rel="preload" href="@rootUrl/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff" as="font" type="font/woff" crossorigin>
<link rel="preload" href="@rootUrl/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="@rootUrl/static/material-icons/web-font/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" as="font" type="font/woff2" crossorigin>
<style> <style>

View File

@ -1,6 +1,8 @@
.router-link-active { .router-link-active {
text-decoration: none !important; text-decoration: none !important;
} }
.material-icons { .material-icons {
font-family: 'Material Icons', "Roboto", "-apple-system", "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: 'Material Icons', "Roboto", "-apple-system", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal; font-weight: normal;

View File

@ -0,0 +1,16 @@
#profile-wrap {
cursor: pointer;
margin-left: auto;
}
.profile-link {
text-decoration: none;
}
.profileMenuContainer {
position: absolute;
background-color: #505050;
padding: 12px 10px 12px;
right: 0;
border: 2px solid #0f0f0f;
border-radius: 10px;
}