Compare commits
10 Commits
ModernAuth
...
features/a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7541caf8cc | |||
| e07f3f9541 | |||
| 846bb29914 | |||
| 6df1ca6d8f | |||
| e35125c306 | |||
| fac6f41910 | |||
| 52381dcb89 | |||
| de3b03258f | |||
| 614f64ddbb | |||
| 29c33d015d |
256
.gitignore
vendored
256
.gitignore
vendored
@@ -1,180 +1,7 @@
|
|||||||
### VisualStudioCode template
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
|
||||||
|
|
||||||
### JetBrains+all template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### VisualStudio template
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
##
|
##
|
||||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.rsuser
|
*.rsuser
|
||||||
@@ -182,6 +9,9 @@ fabric.properties
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
BlazorEcommerce.db
|
||||||
|
BlazorEcommerce.db-shm
|
||||||
|
BlazorEcommerce.db-wal
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
@@ -196,7 +26,6 @@ mono_crash.*
|
|||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
[Aa][Rr][Mm]/
|
||||||
[Aa][Rr][Mm]64/
|
[Aa][Rr][Mm]64/
|
||||||
bld/
|
bld/
|
||||||
@@ -235,9 +64,6 @@ project.lock.json
|
|||||||
project.fragment.lock.json
|
project.fragment.lock.json
|
||||||
artifacts/
|
artifacts/
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
# StyleCop
|
||||||
StyleCopReport.xml
|
StyleCopReport.xml
|
||||||
|
|
||||||
@@ -263,7 +89,6 @@ StyleCopReport.xml
|
|||||||
*.tmp_proj
|
*.tmp_proj
|
||||||
*_wpftmp.csproj
|
*_wpftmp.csproj
|
||||||
*.log
|
*.log
|
||||||
*.tlog
|
|
||||||
*.vspscc
|
*.vspscc
|
||||||
*.vssscc
|
*.vssscc
|
||||||
.builds
|
.builds
|
||||||
@@ -315,11 +140,6 @@ _TeamCity*
|
|||||||
.axoCover/*
|
.axoCover/*
|
||||||
!.axoCover/settings.json
|
!.axoCover/settings.json
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*.json
|
|
||||||
coverage*.xml
|
|
||||||
coverage*.info
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
# Visual Studio code coverage results
|
||||||
*.coverage
|
*.coverage
|
||||||
*.coveragexml
|
*.coveragexml
|
||||||
@@ -467,17 +287,6 @@ node_modules/
|
|||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
*.vbw
|
*.vbw
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
|
||||||
*.vbp
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
|
||||||
*.dsw
|
|
||||||
*.dsp
|
|
||||||
|
|
||||||
# Visual Studio 6 technical files
|
|
||||||
*.ncb
|
|
||||||
*.aps
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
# Visual Studio LightSwitch build output
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
@@ -534,9 +343,6 @@ ASALocalRun/
|
|||||||
# Local History for Visual Studio
|
# Local History for Visual Studio
|
||||||
.localhistory/
|
.localhistory/
|
||||||
|
|
||||||
# Visual Studio History (VSHistory) files
|
|
||||||
.vshistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
# BeatPulse healthcheck temp database
|
||||||
healthchecksdb
|
healthchecksdb
|
||||||
|
|
||||||
@@ -546,31 +352,6 @@ MigrationBackup/
|
|||||||
# Ionide (cross platform F# VS Code tools) working folder
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
.ionide/
|
.ionide/
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
# VS Code files for those working on multiple tools
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
|
||||||
.history/
|
|
||||||
|
|
||||||
# Windows Installer files from build outputs
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
### JetBrains+iml template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
@@ -649,3 +430,32 @@ fabric.properties
|
|||||||
# Android studio 3.1+ serialized cache file
|
# Android studio 3.1+ serialized cache file
|
||||||
.idea/caches/build_file_checksums.ser
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
#User Specific
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
|
||||||
|
#Mono Project Files
|
||||||
|
*.pidb
|
||||||
|
*.resources
|
||||||
|
test-results/
|
||||||
|
.idea/**/.idea/*
|
||||||
|
.vscode/**
|
||||||
|
|
||||||
|
stripe*.json
|
||||||
|
stripe.exe
|
||||||
|
|
||||||
|
# Sqlite database
|
||||||
|
BlazorPolicyAuth/blazorpolicyauth.db
|
||||||
42
BlazorPolicyAuth/App.Services/AuthService/AuthService.cs
Normal file
42
BlazorPolicyAuth/App.Services/AuthService/AuthService.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.App.Services.AuthService;
|
||||||
|
|
||||||
|
public class AuthService(HttpClient httpClient, AuthenticationStateProvider authenticationStateProvider)
|
||||||
|
: IAuthService
|
||||||
|
{
|
||||||
|
public async Task<ServiceResponse<int>> Register(UserRegister request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/register", request);
|
||||||
|
return await ReadResponse<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<string>> Login(UserLogin request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/login", request);
|
||||||
|
return await ReadResponse<string>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<bool>> ChangePassword(UserChangePassword request)
|
||||||
|
{
|
||||||
|
var result = await httpClient.PostAsJsonAsync("api/auth/change-password", request.Password);
|
||||||
|
return await ReadResponse<bool>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsUserAuthenticated()
|
||||||
|
{
|
||||||
|
return (await authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity.IsAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ServiceResponse<T>> ReadResponse<T>(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return await response.Content.ReadFromJsonAsync<ServiceResponse<T>>();
|
||||||
|
|
||||||
|
return new ServiceResponse<T> { Success = false, Message = await response.Content.ReadAsStringAsync() };
|
||||||
|
}
|
||||||
|
}
|
||||||
12
BlazorPolicyAuth/App.Services/AuthService/IAuthService.cs
Normal file
12
BlazorPolicyAuth/App.Services/AuthService/IAuthService.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.App.Services.AuthService;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<ServiceResponse<int>> Register(UserRegister request);
|
||||||
|
Task<ServiceResponse<string>> Login(UserLogin request);
|
||||||
|
Task<ServiceResponse<bool>> ChangePassword(UserChangePassword request);
|
||||||
|
Task<bool> IsUserAuthenticated();
|
||||||
|
}
|
||||||
@@ -8,10 +8,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
|
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Migrations\" />
|
<Folder Include="Migrations\" />
|
||||||
|
<Folder Include="Services\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,79 +1,88 @@
|
|||||||
@page "/login"
|
@page "/login"
|
||||||
@using System.Security.Claims
|
@using System.Web
|
||||||
@using BlazorPolicyAuth.Data
|
@using System.Collections.Specialized
|
||||||
|
@using Blazored.LocalStorage
|
||||||
|
@using Blazored.SessionStorage
|
||||||
|
@using BlazorPolicyAuth.App.Services.AuthService
|
||||||
@using BlazorPolicyAuth.Models.ViewModels
|
@using BlazorPolicyAuth.Models.ViewModels
|
||||||
@using Microsoft.AspNetCore.Authentication
|
@inject IAuthService AuthService
|
||||||
@using Microsoft.AspNetCore.Authentication.Cookies
|
@inject ILocalStorageService LocalStorageService
|
||||||
@using Microsoft.EntityFrameworkCore
|
@inject ISessionStorageService SessionStorageService
|
||||||
@inject AppDbContext DbContext
|
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||||
|
|
||||||
<div class="row">
|
<PageTitle>Login</PageTitle>
|
||||||
<div class="col-lg-4 offset-lg-4 pt-4 pb-4 border">
|
|
||||||
<EditForm Model="Model" OnValidSubmit="Authenticate" FormName="LoginForm">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<DataAnnotationsValidator/>
|
<div class="col-md-4 p-5 shadow-sm border rounded-3">
|
||||||
<div class="mb-3 text-center flex-column">
|
<h2 class="text-center mb-4 text-primary">Login Form</h2>
|
||||||
<img src="/images/login.png" style="max-height:5rem;"/>
|
<EditForm Model="user" OnValidSubmit="HandleLogin" FormName="loginForm">
|
||||||
<h3>LOGIN</h3>
|
<DataAnnotationsValidator />
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<InputText id="email" @bind-Value="user.Email" class="form-control border border-primary" />
|
||||||
|
<ValidationMessage For="@(() => user.Email)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>User Name</label>
|
<label for="password">Password</label>
|
||||||
<InputText @bind-Value="Model.UserName" class="form-control" placeholder="Enter User Name"/>
|
<InputText id="password" @bind-Value="user.Password" class="form-control border border-primary" type="password" />
|
||||||
<ValidationMessage For="() => Model.UserName"></ValidationMessage>
|
<ValidationMessage For="@(() => user.Password)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>Password</label>
|
<label for="rememberme">Remember me</label>
|
||||||
<InputText @bind-Value="Model.Password" class="form-control" placeholder="Enter Password"/>
|
<InputCheckbox id="rememberme" @bind-Value="rememberMe" class="form-check-input" />
|
||||||
<ValidationMessage For="() => Model.Password"></ValidationMessage>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 text-center">
|
<div class="d-grid">
|
||||||
<span class="text-danger">@_errorMessage</span>
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 d-grid gap-2">
|
<div class="mt-3">
|
||||||
<button class="btn btn-primary" type="submit">Login</button>
|
<a href="#">Forgot password</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<p class="mb-0 text-center">You don't have an account? <a href="register" class="text-primary fw-bold">Register</a></p>
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-danger">
|
||||||
|
<span>@errorMessage</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[CascadingParameter]
|
[SupplyParameterFromForm] private UserLogin user { get; set; }
|
||||||
public HttpContext? HttpContext { get; set; }
|
private string errorMessage = string.Empty;
|
||||||
|
private string returnUrl = string.Empty;
|
||||||
[SupplyParameterFromForm]
|
private bool rememberMe;
|
||||||
public LoginViewModel Model { get; set; } = new();
|
|
||||||
|
protected override void OnInitialized()
|
||||||
private string? _errorMessage;
|
|
||||||
|
|
||||||
private async Task Authenticate()
|
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(Model.UserName) || string.IsNullOrWhiteSpace(Model.Password))
|
user ??= new();
|
||||||
|
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
|
||||||
|
NameValueCollection queryStringCall = HttpUtility.ParseQueryString(uri.Query);
|
||||||
|
|
||||||
|
if (queryStringCall.AllKeys.Contains("returnUrl"))
|
||||||
{
|
{
|
||||||
_errorMessage = "Invalid User Name or Password";
|
returnUrl = queryStringCall["returnUrl"];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var userAccount = DbContext.UserAccounts.FirstOrDefault(x => x.UserName == Model.UserName);
|
private async Task HandleLogin()
|
||||||
if (userAccount is null || userAccount.Password != Model.Password)
|
{
|
||||||
|
var result = await AuthService.Login(user);
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
_errorMessage = "Invalid User Name or Password";
|
errorMessage = string.Empty;
|
||||||
return;
|
if (rememberMe)
|
||||||
|
await LocalStorageService.SetItemAsync("authToken", result.Data);
|
||||||
|
else
|
||||||
|
await SessionStorageService.SetItemAsync("authToken", result.Data);
|
||||||
|
|
||||||
|
await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
NavigationManager.NavigateTo(returnUrl);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.Name, Model.UserName)
|
errorMessage = result.Message;
|
||||||
};
|
}
|
||||||
|
|
||||||
/* Add Policies */
|
|
||||||
var userAccountPolicies = await DbContext.UserAccountPolicies.Where(x => x.UserAccountId == userAccount.Id && x.IsEnabled).ToListAsync();
|
|
||||||
claims.AddRange(userAccountPolicies.Select(userAccountPolicy => new Claim(userAccountPolicy.UserPolicy, "true")));
|
|
||||||
|
|
||||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
|
||||||
var principal = new ClaimsPrincipal(identity);
|
|
||||||
await HttpContext?.SignInAsync(principal)!;
|
|
||||||
NavigationManager.NavigateTo("/");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/logout"
|
@page "/logout"
|
||||||
@using Microsoft.AspNetCore.Authentication
|
@using Microsoft.AspNetCore.Authentication
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
68
BlazorPolicyAuth/Components/Pages/Account/Register.razor
Normal file
68
BlazorPolicyAuth/Components/Pages/Account/Register.razor
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
@page "/register"
|
||||||
|
@using BlazorPolicyAuth.App.Services.AuthService
|
||||||
|
@using BlazorPolicyAuth.Models.ViewModels
|
||||||
|
|
||||||
|
@inject IAuthService AuthService
|
||||||
|
|
||||||
|
<title>Register</title>
|
||||||
|
|
||||||
|
<EditForm Model="user" OnValidSubmit="HandleRegistration" OnInvalidSubmit="HandleInvalidSubmit" FormName="registerForm">
|
||||||
|
<DataAnnotationsValidator/>
|
||||||
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
|
<div class="col-md-4 p-5 shadow-sm border rounded-3">
|
||||||
|
<h2 class="text-center mb-4 text-primary">Register Form</h2>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email address</label>
|
||||||
|
<InputText type="email" @bind-Value="user.Email" class="form-control border border-primary" id="email" aria-describedby="email"/>
|
||||||
|
<ValidationMessage For="@(() => user.Email)"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<InputText type="password" @bind-Value="user.Password" class="form-control border border-primary" id="password" aria-describedby="password"/>
|
||||||
|
<ValidationMessage For="@(() => user.Password)"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="confirmPassword" class="form-label">Confirm password</label>
|
||||||
|
<InputText type="password" @bind-Value="user.ConfirmPassword" class="form-control border border-primary" id="confirmPassword" aria-describedby="confirmPassword"/>
|
||||||
|
<ValidationMessage For="@(() => user.ConfirmPassword)"/>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button class="btn btn-primary" type="submit">Register</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<p class="mb-0 text-center">You have an account? <a href="login" class="text-primary fw-bold">Sign In</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<div class="text-danger">
|
||||||
|
<span>@errorMessage</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="@messageCssClass">
|
||||||
|
<span>@message</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[SupplyParameterFromForm] private UserRegister user { get; set; }
|
||||||
|
|
||||||
|
private string message = string.Empty;
|
||||||
|
private string messageCssClass = string.Empty;
|
||||||
|
private string errorMessage = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized() => user ??= new();
|
||||||
|
|
||||||
|
async Task HandleRegistration()
|
||||||
|
{
|
||||||
|
var result = await AuthService.Register(user);
|
||||||
|
message = result.Message;
|
||||||
|
messageCssClass = result.Success ? "text-success" : "text-danger";
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleInvalidSubmit()
|
||||||
|
{
|
||||||
|
message = "Data annotations validation failed.";
|
||||||
|
messageCssClass = "text-danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/Error"
|
@page "/Error"
|
||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
|
|
||||||
<PageTitle>Error</PageTitle>
|
<PageTitle>Error</PageTitle>
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,4 @@
|
|||||||
@using BlazorPolicyAuth
|
@using BlazorPolicyAuth
|
||||||
@using BlazorPolicyAuth.Components
|
@using BlazorPolicyAuth.Components
|
||||||
@using BlazorPolicyAuth.Components.Layout
|
@using BlazorPolicyAuth.Components.Layout
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@@ -10,49 +10,49 @@ public class AppDbContext(DbContextOptions dbContextOptions) : DbContext(dbConte
|
|||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
var demoUserAccounts = new UserAccount[]
|
// var demoUserAccounts = new UserAccount[]
|
||||||
{
|
// {
|
||||||
new() {Id = 1, UserName = "user1", Password = "user1"},
|
// new() {Id = 1, Email = "user1", Password = "user1"},
|
||||||
new() {Id = 2, UserName = "user2", Password = "user2"},
|
// new() {Id = 2, Email = "user2", Password = "user2"},
|
||||||
new() {Id = 3, UserName = "user3", Password = "user3"},
|
// new() {Id = 3, Email = "user3", Password = "user3"},
|
||||||
new() {Id = 4, UserName = "user4", Password = "user4"},
|
// new() {Id = 4, Email = "user4", Password = "user4"},
|
||||||
new() {Id = 5, UserName = "user5", Password = "user5"},
|
// new() {Id = 5, Email = "user5", Password = "user5"},
|
||||||
};
|
// };
|
||||||
modelBuilder.Entity<UserAccount>().HasData(demoUserAccounts);
|
// modelBuilder.Entity<UserAccount>().HasData(demoUserAccounts);
|
||||||
|
//
|
||||||
var demoUserAccountPolicies = new UserAccountPolicy[]
|
// var demoUserAccountPolicies = new UserAccountPolicy[]
|
||||||
{
|
// {
|
||||||
/* User 1 Policies */
|
// /* User 1 Policies */
|
||||||
new() {Id = 1, UserAccountId = 1, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = false},
|
// new() {Id = 1, UserAccountId = 1, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 2, UserAccountId = 1, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = false},
|
// new() {Id = 2, UserAccountId = 1, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 3, UserAccountId = 1, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
// new() {Id = 3, UserAccountId = 1, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 4, UserAccountId = 1, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
// new() {Id = 4, UserAccountId = 1, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
||||||
|
//
|
||||||
/* User 2 Policies */
|
// /* User 2 Policies */
|
||||||
new() {Id = 5, UserAccountId = 2, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
// new() {Id = 5, UserAccountId = 2, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 6, UserAccountId = 2, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = false},
|
// new() {Id = 6, UserAccountId = 2, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 7, UserAccountId = 2, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
// new() {Id = 7, UserAccountId = 2, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 8, UserAccountId = 2, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
// new() {Id = 8, UserAccountId = 2, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
||||||
|
//
|
||||||
/* User 3 Policies */
|
// /* User 3 Policies */
|
||||||
new() {Id = 9, UserAccountId = 3, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
// new() {Id = 9, UserAccountId = 3, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 10, UserAccountId = 3, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
// new() {Id = 10, UserAccountId = 3, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 11, UserAccountId = 3, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
// new() {Id = 11, UserAccountId = 3, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = false},
|
||||||
new() {Id = 12, UserAccountId = 3, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
// new() {Id = 12, UserAccountId = 3, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
||||||
|
//
|
||||||
/* User 4 Policies */
|
// /* User 4 Policies */
|
||||||
new() {Id = 13, UserAccountId = 4, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
// new() {Id = 13, UserAccountId = 4, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 14, UserAccountId = 4, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
// new() {Id = 14, UserAccountId = 4, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 15, UserAccountId = 4, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = true},
|
// new() {Id = 15, UserAccountId = 4, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 16, UserAccountId = 4, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
// new() {Id = 16, UserAccountId = 4, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = false},
|
||||||
|
//
|
||||||
/* User 5 Policies */
|
// /* User 5 Policies */
|
||||||
new() {Id = 17, UserAccountId = 5, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
// new() {Id = 17, UserAccountId = 5, UserPolicy = UserPolicy.VIEW_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 18, UserAccountId = 5, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
// new() {Id = 18, UserAccountId = 5, UserPolicy = UserPolicy.ADD_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 19, UserAccountId = 5, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = true},
|
// new() {Id = 19, UserAccountId = 5, UserPolicy = UserPolicy.EDIT_PRODUCT, IsEnabled = true},
|
||||||
new() {Id = 20, UserAccountId = 5, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = true},
|
// new() {Id = 20, UserAccountId = 5, UserPolicy = UserPolicy.DELETE_PRODUCT, IsEnabled = true},
|
||||||
};
|
// };
|
||||||
modelBuilder.Entity<UserAccountPolicy>().HasData(demoUserAccountPolicies);
|
// modelBuilder.Entity<UserAccountPolicy>().HasData(demoUserAccountPolicies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<UserAccount> UserAccounts { get; set; }
|
public DbSet<UserAccount> UserAccounts { get; set; }
|
||||||
|
|||||||
61
BlazorPolicyAuth/HttpClientSetupService.cs
Normal file
61
BlazorPolicyAuth/HttpClientSetupService.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth;
|
||||||
|
/// <summary>
|
||||||
|
/// Source: https://www.duracellko.net/posts/2020/06/hosting-both-blazor-server-and-webassembly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpClient"></param>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="applicationLifetime"></param>
|
||||||
|
public class HttpClientSetupService(
|
||||||
|
HttpClient httpClient,
|
||||||
|
IServer server,
|
||||||
|
IHostApplicationLifetime applicationLifetime)
|
||||||
|
: BackgroundService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||||
|
private readonly IServer _server = server ?? throw new ArgumentNullException(nameof(server));
|
||||||
|
private readonly IHostApplicationLifetime _applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
|
||||||
|
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var applicationStartedToken = _applicationLifetime.ApplicationStarted;
|
||||||
|
if (applicationStartedToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
ConfigureHttpClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationStartedToken.Register(ConfigureHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureHttpClient()
|
||||||
|
{
|
||||||
|
var serverAddresses = _server.Features.Get<IServerAddressesFeature>();
|
||||||
|
var address = serverAddresses.Addresses.FirstOrDefault();
|
||||||
|
if (address == null)
|
||||||
|
{
|
||||||
|
// Default ASP.NET Core Kestrel endpoint
|
||||||
|
address = "http://localhost:5000";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
address = address.Replace("*", "localhost", StringComparison.Ordinal);
|
||||||
|
address = address.Replace("+", "localhost", StringComparison.Ordinal);
|
||||||
|
address = address.Replace("[::]", "localhost", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseUri = new Uri(address);
|
||||||
|
_httpClient.BaseAddress = baseUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using BlazorPolicyAuth.Data;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace BlazorPolicyAuth.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20251215131042_user account with policies")]
|
|
||||||
partial class useraccountwithpolicies
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.1");
|
|
||||||
|
|
||||||
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccount", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
b.Property<string>("Password")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("password");
|
|
||||||
|
|
||||||
b.Property<string>("UserName")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("user_name");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("user_account");
|
|
||||||
|
|
||||||
b.HasData(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
Password = "user1",
|
|
||||||
UserName = "user1"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 2,
|
|
||||||
Password = "user2",
|
|
||||||
UserName = "user2"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 3,
|
|
||||||
Password = "user3",
|
|
||||||
UserName = "user3"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 4,
|
|
||||||
Password = "user4",
|
|
||||||
UserName = "user4"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 5,
|
|
||||||
Password = "user5",
|
|
||||||
UserName = "user5"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccountPolicy", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
b.Property<bool>("IsEnabled")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("is_enabled");
|
|
||||||
|
|
||||||
b.Property<int>("UserAccountId")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("user_account_policy");
|
|
||||||
|
|
||||||
b.Property<string>("UserPolicy")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("user_policy");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("user_account_policy");
|
|
||||||
|
|
||||||
b.HasData(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 2,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 3,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 4,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 5,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 6,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 7,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 8,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 9,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 10,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 11,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 12,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 13,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 14,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 15,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 16,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 17,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 18,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 19,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 20,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
|
||||||
|
|
||||||
namespace BlazorPolicyAuth.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class useraccountwithpolicies : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "user_account",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
user_name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
password = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_user_account", x => x.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "user_account_policy",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
user_account_policy = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
user_policy = table.Column<string>(type: "TEXT", nullable: true),
|
|
||||||
is_enabled = table.Column<bool>(type: "INTEGER", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_user_account_policy", x => x.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.InsertData(
|
|
||||||
table: "user_account",
|
|
||||||
columns: new[] { "id", "password", "user_name" },
|
|
||||||
values: new object[,]
|
|
||||||
{
|
|
||||||
{ 1, "user1", "user1" },
|
|
||||||
{ 2, "user2", "user2" },
|
|
||||||
{ 3, "user3", "user3" },
|
|
||||||
{ 4, "user4", "user4" },
|
|
||||||
{ 5, "user5", "user5" }
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.InsertData(
|
|
||||||
table: "user_account_policy",
|
|
||||||
columns: new[] { "id", "is_enabled", "user_account_policy", "user_policy" },
|
|
||||||
values: new object[,]
|
|
||||||
{
|
|
||||||
{ 1, false, 1, "VIEW_PRODUCT" },
|
|
||||||
{ 2, false, 1, "ADD_PRODUCT" },
|
|
||||||
{ 3, false, 1, "EDIT_PRODUCT" },
|
|
||||||
{ 4, false, 1, "DELETE_PRODUCT" },
|
|
||||||
{ 5, true, 2, "VIEW_PRODUCT" },
|
|
||||||
{ 6, false, 2, "ADD_PRODUCT" },
|
|
||||||
{ 7, false, 2, "EDIT_PRODUCT" },
|
|
||||||
{ 8, false, 2, "DELETE_PRODUCT" },
|
|
||||||
{ 9, true, 3, "VIEW_PRODUCT" },
|
|
||||||
{ 10, true, 3, "ADD_PRODUCT" },
|
|
||||||
{ 11, false, 3, "EDIT_PRODUCT" },
|
|
||||||
{ 12, false, 3, "DELETE_PRODUCT" },
|
|
||||||
{ 13, true, 4, "VIEW_PRODUCT" },
|
|
||||||
{ 14, true, 4, "ADD_PRODUCT" },
|
|
||||||
{ 15, true, 4, "EDIT_PRODUCT" },
|
|
||||||
{ 16, false, 4, "DELETE_PRODUCT" },
|
|
||||||
{ 17, true, 5, "VIEW_PRODUCT" },
|
|
||||||
{ 18, true, 5, "ADD_PRODUCT" },
|
|
||||||
{ 19, true, 5, "EDIT_PRODUCT" },
|
|
||||||
{ 20, true, 5, "DELETE_PRODUCT" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "user_account");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "user_account_policy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
86
BlazorPolicyAuth/Migrations/20260130211227_createUserAccount.Designer.cs
generated
Normal file
86
BlazorPolicyAuth/Migrations/20260130211227_createUserAccount.Designer.cs
generated
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using BlazorPolicyAuth.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260130211227_createUserAccount")]
|
||||||
|
partial class createUserAccount
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.1");
|
||||||
|
|
||||||
|
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccount", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("email");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("password");
|
||||||
|
|
||||||
|
b.Property<byte[]>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<byte[]>("PasswordSalt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("user_account");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccountPolicy", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("is_enabled");
|
||||||
|
|
||||||
|
b.Property<int>("UserAccountId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("user_account_policy");
|
||||||
|
|
||||||
|
b.Property<string>("UserPolicy")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("user_policy");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("user_account_policy");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class createUserAccount : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "user_account",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
email = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||||
|
password = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
PasswordHash = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||||
|
PasswordSalt = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||||
|
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
Role = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_user_account", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "user_account_policy",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
user_account_policy = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
user_policy = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
is_enabled = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_user_account_policy", x => x.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "user_account");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "user_account_policy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using BlazorPolicyAuth.Data;
|
using BlazorPolicyAuth.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
@@ -23,51 +24,34 @@ namespace BlazorPolicyAuth.Migrations
|
|||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("email");
|
||||||
|
|
||||||
b.Property<string>("Password")
|
b.Property<string>("Password")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("password");
|
.HasColumnName("password");
|
||||||
|
|
||||||
b.Property<string>("UserName")
|
b.Property<byte[]>("PasswordHash")
|
||||||
.HasMaxLength(100)
|
.IsRequired()
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("BLOB");
|
||||||
.HasColumnName("user_name");
|
|
||||||
|
b.Property<byte[]>("PasswordSalt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("user_account");
|
b.ToTable("user_account");
|
||||||
|
|
||||||
b.HasData(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
Password = "user1",
|
|
||||||
UserName = "user1"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 2,
|
|
||||||
Password = "user2",
|
|
||||||
UserName = "user2"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 3,
|
|
||||||
Password = "user3",
|
|
||||||
UserName = "user3"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 4,
|
|
||||||
Password = "user4",
|
|
||||||
UserName = "user4"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 5,
|
|
||||||
Password = "user5",
|
|
||||||
UserName = "user5"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccountPolicy", b =>
|
modelBuilder.Entity("BlazorPolicyAuth.Models.Entities.UserAccountPolicy", b =>
|
||||||
@@ -92,148 +76,6 @@ namespace BlazorPolicyAuth.Migrations
|
|||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("user_account_policy");
|
b.ToTable("user_account_policy");
|
||||||
|
|
||||||
b.HasData(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 2,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 3,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 4,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 1,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 5,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 6,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 7,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 8,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 2,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 9,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 10,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 11,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 12,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 3,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 13,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 14,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 15,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 16,
|
|
||||||
IsEnabled = false,
|
|
||||||
UserAccountId = 4,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 17,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "VIEW_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 18,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "ADD_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 19,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "EDIT_PRODUCT"
|
|
||||||
},
|
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = 20,
|
|
||||||
IsEnabled = true,
|
|
||||||
UserAccountId = 5,
|
|
||||||
UserPolicy = "DELETE_PRODUCT"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace BlazorPolicyAuth.Models.Entities;
|
namespace BlazorPolicyAuth.Models.Entities;
|
||||||
@@ -11,11 +12,17 @@ public class UserAccount
|
|||||||
[Column("id")]
|
[Column("id")]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Column("user_name")]
|
[Column("email")]
|
||||||
[MaxLength(100)]
|
[MaxLength(200)]
|
||||||
public string? UserName { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
[Column("password")]
|
[Column("password")]
|
||||||
[MaxLength(100)]
|
[MaxLength(100)]
|
||||||
public string? Password { get; set; }
|
public string? Password { get; set; }
|
||||||
|
|
||||||
|
public byte[] PasswordHash { get; set; } = [];
|
||||||
|
public byte[] PasswordSalt { get; set; } = [];
|
||||||
|
|
||||||
|
public DateTime DateCreated { get; set; } = DateTime.Now;
|
||||||
|
public string Role { get; set; } = "User";
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace BlazorPolicyAuth.Models.ViewModels;
|
|
||||||
|
|
||||||
public class LoginViewModel
|
|
||||||
{
|
|
||||||
public string? UserName { get; set; }
|
|
||||||
public string? Password { get; set; }
|
|
||||||
}
|
|
||||||
9
BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs
Normal file
9
BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace BlazorPolicyAuth.Models.ViewModels
|
||||||
|
{
|
||||||
|
public class ServiceResponse<T>
|
||||||
|
{
|
||||||
|
public T? Data { get; set; }
|
||||||
|
public bool Success { get; set; } = true;
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs
Normal file
12
BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Models.ViewModels
|
||||||
|
{
|
||||||
|
public class UserChangePassword
|
||||||
|
{
|
||||||
|
[Required, StringLength(100, MinimumLength = 5)]
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
[Compare("Password", ErrorMessage = "The password do not match")]
|
||||||
|
public string ConfirmPassword { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
BlazorPolicyAuth/Models/ViewModels/UserLogin.cs
Normal file
11
BlazorPolicyAuth/Models/ViewModels/UserLogin.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
|
||||||
|
public class UserLogin
|
||||||
|
{
|
||||||
|
[Required, EmailAddress]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
[Required]
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
14
BlazorPolicyAuth/Models/ViewModels/UserRegister.cs
Normal file
14
BlazorPolicyAuth/Models/ViewModels/UserRegister.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Models.ViewModels
|
||||||
|
{
|
||||||
|
public class UserRegister
|
||||||
|
{
|
||||||
|
[Required, EmailAddress]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
[Required, StringLength(100, MinimumLength = 5)]
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
[Compare("Password", ErrorMessage = "The password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Blazored.LocalStorage;
|
||||||
|
using Blazored.SessionStorage;
|
||||||
using BlazorPolicyAuth;
|
using BlazorPolicyAuth;
|
||||||
using BlazorPolicyAuth.Components;
|
using BlazorPolicyAuth.Components;
|
||||||
using BlazorPolicyAuth.Data;
|
using BlazorPolicyAuth.Data;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
using BlazorPolicyAuth.Services.AuthService;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using ClientServices = BlazorPolicyAuth.App.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -30,6 +41,20 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddCascadingAuthenticationState();
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
builder.Services.AddBlazoredLocalStorage();
|
||||||
|
builder.Services.AddBlazoredSessionStorage();
|
||||||
|
|
||||||
|
// Blazor client services
|
||||||
|
builder.Services.AddScoped<ClientServices.AuthService.IAuthService, ClientServices.AuthService.AuthService>();
|
||||||
|
|
||||||
|
// Blazor server services
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
|
||||||
|
// Get server base address when application starts to properly configure HttpClient for client service to call server service
|
||||||
|
builder.Services.AddSingleton<HttpClient>();
|
||||||
|
builder.Services.AddSingleton<IHostedService, HttpClientSetupService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
@@ -49,4 +74,11 @@ app.MapStaticAssets();
|
|||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
// Blazor server routing
|
||||||
|
app.MapPost("/api/auth/register", async (UserRegister request, IAuthService authService) =>
|
||||||
|
await authService.Register(request));
|
||||||
|
|
||||||
|
app.MapPost("/api/auth/login", async (UserLogin request, IAuthService authService) =>
|
||||||
|
await authService.Login(request.Email, request.Password));
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
138
BlazorPolicyAuth/Services/AuthService/AuthService.cs
Normal file
138
BlazorPolicyAuth/Services/AuthService/AuthService.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BlazorPolicyAuth.Data;
|
||||||
|
using BlazorPolicyAuth.Models.Entities;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
using BlazorPolicyAuth.Services.AuthService;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Services.AuthService;
|
||||||
|
|
||||||
|
public class AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
|
||||||
|
: IAuthService
|
||||||
|
{
|
||||||
|
public async Task<ServiceResponse<int>> Register (UserRegister request)
|
||||||
|
{
|
||||||
|
if (await UserExists(request.Email))
|
||||||
|
{
|
||||||
|
return new ServiceResponse<int> { Success = false, Message = "User already exist." };
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatePasswordHash(request.Password, out byte[] passwordHash, out byte[] passwordSalt);
|
||||||
|
var user = new UserAccount
|
||||||
|
{
|
||||||
|
Email = request.Email,
|
||||||
|
PasswordHash = passwordHash,
|
||||||
|
PasswordSalt = passwordSalt
|
||||||
|
};
|
||||||
|
|
||||||
|
context.UserAccounts.Add(user);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return new ServiceResponse<int> { Data = user.Id, Message = "Registration successful" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UserExists(string email)
|
||||||
|
{
|
||||||
|
if (await context.UserAccounts.AnyAsync(user => user.Email.ToLower().Equals(email.ToLower())))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<string>> Login(string email, string password)
|
||||||
|
{
|
||||||
|
var response = new ServiceResponse<string>();
|
||||||
|
var user = await context.UserAccounts.FirstOrDefaultAsync(u => u.Email.ToLower().Equals(email.ToLower()));
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
response.Success = false;
|
||||||
|
response.Message = "User not found.";
|
||||||
|
}
|
||||||
|
else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
|
||||||
|
{
|
||||||
|
response.Success = false;
|
||||||
|
response.Message = "Wrong password.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Data = CreateToken(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword)
|
||||||
|
{
|
||||||
|
var user = await context.UserAccounts.FindAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return new ServiceResponse<bool> { Success = false, Message = "User not found." };
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatePasswordHash(newPassword, out byte[] passwordHash, out byte[] passwordSalt);
|
||||||
|
|
||||||
|
user.PasswordHash = passwordHash;
|
||||||
|
user.PasswordSalt = passwordSalt;
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return new ServiceResponse<bool> { Data = true, Message = "Password has been changed." };
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetUserId() => int.Parse(httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
|
||||||
|
public string GetUserEmail() => httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
|
||||||
|
public async Task<UserAccount> GetUserByEmail(string email)
|
||||||
|
{
|
||||||
|
return await context.UserAccounts.FirstOrDefaultAsync(u => u.Email.Equals(email));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
||||||
|
{
|
||||||
|
using var hmac = new HMACSHA512();
|
||||||
|
passwordSalt = hmac.Key;
|
||||||
|
passwordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
|
||||||
|
{
|
||||||
|
using var hmac = new HMACSHA512(passwordSalt);
|
||||||
|
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||||
|
return computedHash.SequenceEqual(passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateToken(UserAccount user)
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
|
new(ClaimTypes.Name, user.Email),
|
||||||
|
new(ClaimTypes.Role, user.Role)
|
||||||
|
};
|
||||||
|
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("AppSettings:Token").Value));
|
||||||
|
|
||||||
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.Now.AddDays(1),
|
||||||
|
signingCredentials: creds);
|
||||||
|
|
||||||
|
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
BlazorPolicyAuth/Services/AuthService/IAuthService.cs
Normal file
16
BlazorPolicyAuth/Services/AuthService/IAuthService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using BlazorPolicyAuth.Models.Entities;
|
||||||
|
using BlazorPolicyAuth.Models.ViewModels;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth.Services.AuthService;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<ServiceResponse<int>> Register(UserRegister request);
|
||||||
|
Task<bool> UserExists(string email);
|
||||||
|
Task<ServiceResponse<string>> Login(string email, string password);
|
||||||
|
Task<ServiceResponse<bool>> ChangePassword(int userId, string newPassword);
|
||||||
|
int GetUserId();
|
||||||
|
string GetUserEmail();
|
||||||
|
Task<UserAccount> GetUserByEmail(string email);
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace BlazorPolicyAuth;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BlazorPolicyAuth;
|
||||||
|
|
||||||
public class UserPolicy
|
public class UserPolicy
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,5 +9,8 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"AppSettings" : {
|
||||||
|
"Token": "my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key my top secret key"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user