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"]
path = include/Math-Expression-Evaluator
url = git@git.codeliturgy.com:P0/Math-Expression-Evaluator.git
[submodule "BlueWest.Frontend"]
path = BlueWest.Frontend
url = git@git.codeliturgy.com:P0/BlueWest.Frontend.git
[submodule "CodeLiturgy.Dashboard.Frontend"]
path = CodeLiturgy.Dashboard.Frontend
url = git@git.codeliturgy.com:P0/CodeLiturgy.Dashboard.Frontend.git
[submodule "include/BlueWest.EfMethods"]
path = include/BlueWest.EfMethods
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>
/// API User policy Key
/// </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 CookieDomain = "https://localhost:7022";

View File

@ -3,6 +3,8 @@ namespace BlueWest.Data.Auth.Context.Users;
public static class Constants
{
public const string CorsPolicyName = "_myAllowSpecificOrigins";
/// <summary>
/// JwtClaimIdentifiers
/// </summary>
@ -21,4 +23,14 @@ public static class Constants
/// </summary>
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);
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>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
@ -79,10 +79,10 @@
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt" />
<_ContentIncludedByDefault Remove="Views\Data\Roles\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Views\Account\Index.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\ChangePassword.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\Curriculums\Index.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\Curriculums\Upload.cshtml" />
<_ContentIncludedByDefault Remove="Views\Account\Index.cshtml" />
</ItemGroup>
<ItemGroup>
@ -109,4 +109,9 @@
<Folder Include="wwwroot\static\profile" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Views\Auth\Account.cshtml" />
<AdditionalFiles Include="Views\EnvironmentsPage\Index.cshtml" />
</ItemGroup>
</Project>

View File

@ -12,7 +12,6 @@ public class AccountController : UserController
{
private readonly ILogger<AccountController> _logger;
public AccountController(ApplicationUserManager userManager, ILogger<AccountController> logger) : base(userManager, logger)
{
_userManager = userManager;
@ -27,19 +26,9 @@ public class AccountController : UserController
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()
{
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)]
public async Task<IActionResult> Error()
{

View File

@ -62,6 +62,13 @@ namespace CodeLiturgy.Views.Controllers
return View();
}
public async Task<IActionResult> Account()
{
await OnEveryAction();
this.HandleGlobalization();
return View();
}
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;
}
public override void OnInitialization()
{
var menu = LayoutCache.Root.ChildrenToUrl();
SetHeaderMenu(menu);
}
[AllowAnonymous]
public async Task<IActionResult> Index()

View File

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

View File

@ -15,22 +15,24 @@ public class UserController : Controller
protected ILogger<UserController> _logger;
protected ApplicationUserManager _userManager;
private List<RouteRecord> _footerMenu;
private List<Url> _footerMenu;
private List<Url> _headerMenu;
public UserController(ApplicationUserManager userManager, ILogger<UserController> logger)
{
_logger = logger;
_userManager = userManager;
_footerMenu = new List<RouteRecord>();
_footerMenu = new List<Url>();
_headerMenu = new List<Url>();
}
public async Task OnEveryAction()
{
HandleGlobalization();
SetFooterMenuViewData();
await SetUserProfileViewData();
OnInitialization();
SetFooterMenuViewData();
SetHeaderMenuViewData();
}
public virtual void OnInitialization()
@ -42,9 +44,21 @@ public class UserController : Controller
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()

View File

@ -1,6 +1,6 @@
namespace CodeLiturgy.Views.Languages;
public static class SiteContent
public static class Translations
{
internal static readonly Dictionary<string, Dictionary<string, string>> RouteTitle =
new Dictionary<string, Dictionary<string, string>>
@ -21,14 +21,6 @@ public static class SiteContent
{"en-gb", "System"},
}
},
{
DataKeyName, new Dictionary<string, string>()
{
{"pt", "Dados"},
{"eng", "Data"},
{"en-gb", "Data"}
}
},
{
RolesKeyName, new Dictionary<string, string>()
@ -62,68 +54,23 @@ public static class SiteContent
{"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"},
{"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"},
{"pt", "Conta"},
{"eng", "Account"},
{"en-gb", "Account"},
}
},
{
EnvironmentsKeyName, new Dictionary<string, string>()
{
{"pt", "Ambientes"},
{"eng", "Environments"},
{"en-gb", "Environments"},
}
},
{
ChangePasswordKeyName, new Dictionary<string, string>()

View File

@ -112,6 +112,24 @@ public static class StartupExtensions
{
options.LoginPath = Routes.AuthLoginRoute;
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,
policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol,
Constants.JwtClaims.ApiAccess));
options.AddPolicy(SessionConstants.CookieNamePolicy, policy =>
{
policy.RequireClaim(Constants.CookieClaims.CookieAccess);
});
});
// add identity
@ -168,6 +190,7 @@ public static class StartupExtensions
{
return serviceCollection
.AddDbContextPool<UserDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<SiteDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<CountryDbContext>(options => options.UsePsqlConfiguration(configuration))
.AddDbContextPool<ApplicationUserDbContext>(options =>
options.UsePsqlConfiguration(configuration));

View File

@ -10,16 +10,22 @@ internal class LayoutCache
#region Route Tree
private static readonly RouteRecord NonLoggedInRoot = new RouteRecord(
"Login",
AuthLoginRoute,
AuthLoginKeyName,
nameof(AuthController), new List<RouteRecord>());
private static readonly RouteRecord Root = new RouteRecord(
internal static readonly RouteRecord Root = new RouteRecord(
"Code Liturgy - Dashboard",
RootKeyName,
RootLocation,
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
@ -28,9 +34,6 @@ internal class LayoutCache
internal static readonly RouteRecord SystemRoute =
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 =
Root.Children.FirstOrDefault(x => x.ViewType == ViewType.Account)!;
@ -38,70 +41,48 @@ internal class LayoutCache
#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
.Root
.Children;
menuToShow.Add(Root.ToUrl());
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)
{
var menuToShow = new List<RouteView>();
menuToShow.Add(new RouteView("CodeLiturgy Dashboard", "/"));
return menuToShow;
}
var location = GetUserLanguage(dictionary);
var menu = LayoutCache
.Root
.Children;
if (dictionary[HeaderMenuId] is List<RouteRecord> footerMenu)
if (dictionary[HeaderMenuId] is List<Url> headerMenu)
{
menu = footerMenu;
menuToShow = menuToShow
.Concat(headerMenu)
.ToList();
}
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);
return menuToShow;
}
})
.ToList();
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;
}

View File

@ -1,14 +1,33 @@
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 Location = location;
public string ControllerName;
public List<RouteRecord> Children = children;
public ViewType ViewType = viewType;
public string Name;
public string Location;
public Url(string name, string location)
{
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 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 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 AuthAccountRoute = "/auth/account";
internal const string AuthAccountKeyName = "account";
// Account
internal const string AccountRouteLocation = "/account";
internal const string AccountKeyName = "profile";
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
{
System,
Data,
Account,
Environments,
Root,
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">
<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">Login</span>
<span class="block">Signup</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">

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 Duende.IdentityServer.Extensions
@{
Layout = null;
var menu = LayoutCache.GetDefaultFooterMenu(ViewData);
var menu = LayoutCache.GetDefaultFooterMenu(ViewData, User.IsAuthenticated());
}

View File

@ -4,10 +4,12 @@
@{
Layout = null;
var userAuthenticated = User.IsAuthenticated();
var menu = LayoutCache.GetDefaultHeaderMenu(ViewData, userAuthenticated);
var user = ViewData.GetUserViewData();
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">
@ -26,7 +28,7 @@
}
@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-avatar">
<div class="q-avatar__content row flex-center overflow-hidden">
@ -35,8 +37,46 @@
</div> @user.Email
</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>
}
<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 name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - CodeLiturgy.Views</title>
<link rel="stylesheet" href="@rootUrl/static/main.css"/>
@*
<link rel="stylesheet" href="~/CodeLiturgy.Views.styles.css" asp-append-version="true"/>
*@
<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="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>
<style>

View File

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