diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1ee5385
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,362 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
diff --git a/.pipelines/GitVersion.yml b/.pipelines/GitVersion.yml
new file mode 100644
index 0000000..f5867a9
--- /dev/null
+++ b/.pipelines/GitVersion.yml
@@ -0,0 +1,59 @@
+mode: mainline
+assembly-versioning-scheme: MajorMinorPatch
+tag-prefix: '[vV]'
+major-version-bump-message: '\+semver:\s?(breaking|major)'
+minor-version-bump-message: '\+semver:\s?(feature|minor)'
+patch-version-bump-message: '\+semver:\s?(fix|patch)'
+no-bump-message: '\+semver:\s?(none|skip)'
+legacy-semver-padding: 4
+build-metadata-padding: 4
+commits-since-version-source-padding: 4
+commit-message-incrementing: Enabled
+branches:
+ main:
+ regex: ^master$|^main$
+ tag: ''
+ increment: Patch
+ prevent-increment-of-merged-branch-version: true
+ track-merge-target: false
+ source-branches: [ 'develop', 'release' ]
+ tracks-release-branches: false
+ is-release-branch: false
+ is-mainline: true
+ pre-release-weight: 55000
+ feature:
+ regex: ^features?[/-]
+ tag: useBranchName
+ increment: Inherit
+ prevent-increment-of-merged-branch-version: false
+ track-merge-target: false
+ source-branches: [ 'develop', 'main', 'release', 'feature', 'support', 'hotfix' ]
+ tracks-release-branches: false
+ is-release-branch: false
+ is-mainline: false
+ pre-release-weight: 30000
+ release:
+ regex: ^releases?[/-]
+ tag: beta
+ increment: None
+ is-mainline: true
+ regex: ^releases?[/-]
+ prevent-increment-of-merged-branch-version: true
+ track-merge-target: false
+ source-branches: [ 'develop', 'main', 'support', 'release' ]
+ tracks-release-branches: false
+ is-release-branch: true
+ is-mainline: false
+ pre-release-weight: 30000
+ hotfix:
+ regex: ^hotfix(es)?[/-]
+ increment: Patch
+ prevent-increment-of-merged-branch-version: false
+ track-merge-target: false
+ source-branches: [ 'develop', 'main', 'support' ]
+ tracks-release-branches: false
+ is-release-branch: false
+ is-mainline: false
+ pre-release-weight: 30000
+ignore:
+ sha: []
\ No newline at end of file
diff --git a/.pipelines/templated-pipeline.yml b/.pipelines/templated-pipeline.yml
new file mode 100644
index 0000000..805a9d7
--- /dev/null
+++ b/.pipelines/templated-pipeline.yml
@@ -0,0 +1,46 @@
+pool:
+ vmImage: 'ubuntu-latest'
+
+variables:
+ project: 'Core.Cerberos.Adapters/Core.Cerberos.Adapters.csproj'
+ solution: 'Core.Cerberos.Adapters.sln'
+ buildConfiguration: 'Release'
+ snykConnectionEndpoint: 'SnykConnection'
+ feed: '1b3770f1-17db-4bf2-a43d-49f305aa7a22'
+ artifactName: 'Core.Cerberos'
+ projectFileName: 'Core.Cerberos.Adapters.csproj'
+ projectPath: 'Core.Cerberos.Adapters/'
+
+resources:
+ repositories:
+ - repository: templates
+ name: "Template.DevOps.Pipelines"
+ type: "git"
+
+jobs:
+- job: CI
+ steps:
+ - template: templates/dotnet/v1/step1_setup.yml@templates
+ - template: templates/dotnet/v1/step2_versioning.yml@templates
+ parameters:
+ projectFileName: '$(projectFileName)'
+ path: '$(projectPath)'
+ - template: templates/dotnet/v1/step3_restore_and_build.yml@templates
+ parameters:
+ project: '$(project)'
+ solution: '$(solution)'
+ buildConfiguration: '$(buildConfiguration)'
+ projectNameOnSonar: 'Core.Cerberos'
+ projectKeyOnSonar: 'heathpbu_Core.Cerberos'
+ feed: '$(feed)'
+ - template: templates/dotnet/v1/step4_sonar_analysis.yml@templates
+
+ - template: templates/dotnet/v1/step5_snyk_analysis.yml@templates
+ parameters:
+ snykConnectionEndpoint: '$(snykConnectionEndpoint)'
+ solutionToScan: '$(solution)'
+
+ - template: templates/dotnet/v1/step6_release_nuget.yml@templates
+ parameters:
+ artifactName: '$(artifactName)'
+ project: '$(project)'
diff --git a/Core.Cerberos.Adapters.sln b/Core.Cerberos.Adapters.sln
new file mode 100644
index 0000000..34a1919
--- /dev/null
+++ b/Core.Cerberos.Adapters.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.34928.147
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.Adapters", "Core.Cerberos.Adapters\Core.Cerberos.Adapters.csproj", "{C902AB37-E6D1-4CE7-B271-0E3969C989F3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5BADECD6-CE7F-4167-A29F-AC3D7E4FE4D0}
+ EndGlobalSection
+EndGlobal
diff --git a/Core.Cerberos.Adapters/Adapters/Base/BaseAdapterResponse.cs b/Core.Cerberos.Adapters/Adapters/Base/BaseAdapterResponse.cs
new file mode 100644
index 0000000..5a67d10
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/Base/BaseAdapterResponse.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Core.Cerberos.Adapters
+{
+ public class BaseAdapterResponse
+ {
+ public bool HasError { get; set; } = false;
+ public bool IsSuccess { get; private set; } = false;
+ public string Message { get; set; }
+
+ public void SetResult(string message)
+ {
+ HasError = false;
+ IsSuccess = true;
+ Message = message;
+ }
+ public string SetErrorMessage(string message)
+ {
+ var _message = new
+ {
+ Content = JsonSerializer.Serialize(message),
+ HasError = true
+ };
+
+ return JsonSerializer.Serialize(_message);
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Adapters/ModuleAdapter.cs b/Core.Cerberos.Adapters/Adapters/ModuleAdapter.cs
new file mode 100644
index 0000000..717bc20
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/ModuleAdapter.cs
@@ -0,0 +1,118 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Common.Enums;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters
+{
+ ///
+ /// Adapter for representing a module.
+ ///
+ public class ModuleAdapter
+ {
+ ///
+ /// Gets or sets the ID of the module.
+ ///
+ [BsonId]
+ [BsonElement("_id")]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = null!;
+
+ ///
+ /// Gets or sets the name of the module.
+ ///
+ [BsonElement("name")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Gets or sets the description of the module.
+ ///
+ [BsonElement("description")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the description of the module.
+ ///
+ [BsonElement("icon")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("icon")]
+ public string? Icon { get; set; }
+
+ ///
+ /// Gets or sets the description of the module.
+ ///
+ [BsonElement("route")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("route")]
+ public string Route { get; set; } = null!;
+
+ ///
+ /// Gets or sets the order of the module.
+ ///
+ [BsonElement("order")]
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+
+ ///
+ /// Gets or sets the application of the module.
+ ///
+ [BsonElement("application")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("application")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ApplicationsEnum? Application { get; set; } = null!;
+
+ ///
+ /// Gets or sets the date and time when the module was created.
+ ///
+ [BsonElement("createdAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// Gets or sets the user who created the module.
+ ///
+ [BsonElement("createdBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("createdBy")]
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// Gets or sets the date and time when the module was last updated.
+ ///
+ [BsonElement("updatedAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("updatedAt")]
+ public DateTime? UpdatedAt { get; set; } = null;
+
+ ///
+ /// Gets or sets the user who last updated the module.
+ ///
+ [BsonElement("updatedBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("updatedBy")]
+ public string? UpdatedBy { get; set; } = null;
+
+ ///
+ /// Gets or sets the status of the module.
+ ///
+ [BsonElement("status")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public StatusEnum Status { get; set; } = StatusEnum.Active;
+ }
+}
diff --git a/Core.Cerberos.Adapters/Adapters/PermissionAdapter.cs b/Core.Cerberos.Adapters/Adapters/PermissionAdapter.cs
new file mode 100644
index 0000000..7499119
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/PermissionAdapter.cs
@@ -0,0 +1,95 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Common.Constants;
+using Core.Cerberos.Adapters.Common.Enums;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters
+{
+ ///
+ /// Adapter for representing a permission.
+ ///
+ public class PermissionAdapter
+ {
+ ///
+ /// Gets or sets the ID of the entity.
+ ///
+ [BsonId]
+ [BsonElement("_id")]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = null!;
+
+ ///
+ /// Gets or sets the name of the entity.
+ ///
+ [BsonElement("name")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Gets or sets the description of the entity.
+ ///
+ [BsonElement("description")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the status of the entity object.
+ ///
+ [BsonElement("accessLevel")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("accessLevel")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AccessLevelEnum? AccessLevel { get; set; } = null!;
+
+ ///
+ /// Gets or sets the date and time when the entity was created.
+ ///
+ [BsonElement("createdAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// Gets or sets the user who created the entity.
+ ///
+ [BsonElement("createdBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("createdBy")]
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// Gets or sets the date and time when the entity was last updated.
+ ///
+ [BsonElement("updatedAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("updatedAt")]
+ public DateTime? UpdatedAt { get; set; } = null;
+
+ ///
+ /// Gets or sets the user who last updated the entity.
+ ///
+ [BsonElement("updatedBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("updatedBy")]
+ public string? UpdatedBy { get; set; } = null;
+
+ ///
+ /// Gets or sets the status of the entity.
+ ///
+ [BsonElement("status")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public StatusEnum Status { get; set; } = StatusEnum.Active;
+ }
+}
diff --git a/Core.Cerberos.Adapters/Adapters/RoleAdapter.cs b/Core.Cerberos.Adapters/Adapters/RoleAdapter.cs
new file mode 100644
index 0000000..d2c03c7
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/RoleAdapter.cs
@@ -0,0 +1,107 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Common.Enums;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters
+{
+ ///
+ /// Adapter representing a role.
+ ///
+ public class RoleAdapter
+ {
+ ///
+ /// Gets or sets the unique identifier of the role.
+ ///
+ [BsonId]
+ [BsonElement("_id")]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = null!;
+
+ ///
+ /// Gets or sets the name of the role.
+ ///
+ [BsonElement("name")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Gets or sets the description of the role.
+ ///
+ [BsonElement("description")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the status of the entity.
+ ///
+ [BsonElement("applications")]
+ [JsonPropertyName("applications")]
+ [JsonConverter(typeof(EnumArrayJsonConverter))]
+ public ApplicationsEnum[]? Applications { get; set; }
+
+ ///
+ /// Gets or sets the modules of the role.
+ ///
+ [BsonElement("modules")]
+ [JsonPropertyName("modules")]
+ public string[] Modules { get; set; } = null!;
+
+ ///
+ /// Gets or sets the permissions of the role.
+ ///
+ [BsonElement("permissions")]
+ [JsonPropertyName("permissions")]
+ public string[] Permissions { get; set; } = null!;
+
+ ///
+ /// Gets or sets the date and time when the entity was created.
+ ///
+ [BsonElement("createdAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// Gets or sets the user who created the entity.
+ ///
+ [BsonElement("createdBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("createdBy")]
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// Gets or sets the date and time when the entity was last updated.
+ ///
+ [BsonElement("updatedAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("updatedAt")]
+ public DateTime? UpdatedAt { get; set; } = null;
+
+ ///
+ /// Gets or sets the user who last updated the entity.
+ ///
+ [BsonElement("updatedBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("updatedBy")]
+ public string? UpdatedBy { get; set; } = null;
+
+ ///
+ /// Gets or sets the status of the entity.
+ ///
+ [BsonElement("status")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public StatusEnum Status { get; set; } = StatusEnum.Active;
+ }
+}
diff --git a/Core.Cerberos.Adapters/Adapters/TokenAdapter.cs b/Core.Cerberos.Adapters/Adapters/TokenAdapter.cs
new file mode 100644
index 0000000..b7f5b51
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/TokenAdapter.cs
@@ -0,0 +1,18 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+namespace Core.Cerberos.Adapters
+{
+ public class TokenAdapter
+ {
+ public UserAdapter? User { get; set; }
+
+ public RoleAdapter? Role { get; set; }
+
+ public IEnumerable? Permissions { get; set; }
+ public IEnumerable? Modules { get; set; }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Adapters/UserAdapter.cs b/Core.Cerberos.Adapters/Adapters/UserAdapter.cs
new file mode 100644
index 0000000..9afda00
--- /dev/null
+++ b/Core.Cerberos.Adapters/Adapters/UserAdapter.cs
@@ -0,0 +1,171 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+using Core.Cerberos.Adapters.Common.Enums;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters
+{
+ ///
+ /// Adapter representing a user.
+ ///
+ public class UserAdapter : BaseAdapterResponse
+ {
+ ///
+ /// Gets or sets the unique identifier of the user.
+ ///
+ [BsonId]
+ [BsonElement("_id")]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = null!;
+
+ ///
+ /// Gets or sets the guid of the user.
+ ///
+ [BsonElement("guid")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("guid")]
+ public string? Guid { get; set; }
+
+ ///
+ /// Gets or sets the email address of the user.
+ ///
+ [BsonElement("email")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("email")]
+ public string Email { get; set; } = null!;
+
+ ///
+ /// Gets or sets the name of the user.
+ ///
+ [BsonElement("name")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ ///
+ /// Gets or sets the middlename of the user.
+ ///
+ [BsonElement("middleName")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("middleName")]
+ public string? MiddleName { get; set; }
+
+ ///
+ /// Gets or sets the last name of the user.
+ ///
+ [BsonElement("lastName")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("lastName")]
+ public string LastName { get; set; } = null!;
+
+ ///
+ /// Gets or sets the name of the user.
+ ///
+ [BsonElement("displayName")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
+
+ ///
+ /// Gets or sets the role ID of the user.
+ ///
+ [BsonElement("roleId")]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("roleId")]
+ public string RoleId { get; set; } = null!;
+
+ ///
+ /// Gets or sets the array of companies associated with the user.
+ ///
+ [BsonElement("companies")]
+ [JsonPropertyName("companies")]
+ public string[] Companies { get; set; } = null!;
+
+ ///
+ /// Gets or sets the array of projects associated with the user.
+ ///
+ [BsonElement("projects")]
+ [JsonPropertyName("projects")]
+ public string[]? Projects { get; set; }
+
+ ///
+ /// Gets or sets the boolean of the consent form accepted by the user.
+ ///
+ [BsonElement("consentFormAccepted")]
+ [JsonPropertyName("consentFormAccepted")]
+ [BsonIgnoreIfNull]
+ public bool ConsentFormAccepted { get; set; }
+
+ ///
+ /// Gets or sets the timestamp of the last login of the user.
+ ///
+ [BsonElement("lastLogIn")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("lastLogIn")]
+ public DateTime? LastLogIn { get; set; }
+
+ ///
+ /// Gets or sets the timestamp of the last logout of the user.
+ ///
+ [BsonElement("lastLogOut")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("lastLogOut")]
+ public DateTime? LastLogOut { get; set; }
+
+
+ ///
+ /// Gets or sets the token associated with the user.
+ ///
+ [BsonElement("token")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("token")]
+ public string? Token { get; set; } = null;
+
+ ///
+ /// Gets or sets the date and time when the entity was created.
+ ///
+ [BsonElement("createdAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// Gets or sets the user who created the entity.
+ ///
+ [BsonElement("createdBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("createdBy")]
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// Gets or sets the date and time when the entity was last updated.
+ ///
+ [BsonElement("updatedAt")]
+ [BsonRepresentation(BsonType.DateTime)]
+ [JsonPropertyName("updatedAt")]
+ public DateTime? UpdatedAt { get; set; } = null;
+
+ ///
+ /// Gets or sets the user who last updated the entity.
+ ///
+ [BsonElement("updatedBy")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("updatedBy")]
+ public string? UpdatedBy { get; set; } = null;
+
+ ///
+ /// Gets or sets the status of the entity.
+ ///
+ [BsonElement("status")]
+ [BsonRepresentation(BsonType.String)]
+ [JsonPropertyName("status")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public StatusEnum Status { get; set; } = StatusEnum.Active;
+ }
+}
diff --git a/Core.Cerberos.Adapters/Attributes/Permission.cs b/Core.Cerberos.Adapters/Attributes/Permission.cs
new file mode 100644
index 0000000..a531b52
--- /dev/null
+++ b/Core.Cerberos.Adapters/Attributes/Permission.cs
@@ -0,0 +1,9 @@
+namespace Core.Cerberos.Adapters
+{
+ public class Permission
+ {
+ public string Name { get; set; }
+
+ public string AccessLevel { get; set; }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Attributes/PermissionAttribute.cs b/Core.Cerberos.Adapters/Attributes/PermissionAttribute.cs
new file mode 100644
index 0000000..7db7cc5
--- /dev/null
+++ b/Core.Cerberos.Adapters/Attributes/PermissionAttribute.cs
@@ -0,0 +1,72 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace Core.Cerberos.Adapters.Attributes
+{
+ ///
+ /// Custom authorization attribute that checks if the user has any of the required permissions.
+ ///
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public class PermissionAttribute : AuthorizeAttribute, IAuthorizationFilter
+ {
+ private readonly string _requiredPermissions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array of permissions required to access the resource.
+ public PermissionAttribute(string requiredPermissions)
+ {
+ _requiredPermissions = requiredPermissions;
+ }
+
+ ///
+ /// Called during the authorization process to determine if the user has any of the required permissions.
+ ///
+ /// The context in which the authorization filter operates.
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ try
+ {
+ var hasPermission = false;
+
+ var servicePermissionsList = _requiredPermissions.Replace(" ", "").Split(',').ToList();
+
+ var servicePermissions = servicePermissionsList.Select(s => new Permission
+ {
+ Name = s.Substring(0, s.IndexOf('.')),
+ AccessLevel = s.Substring(s.IndexOf('.') + 1),
+ });
+
+ var userPermissionsList = context.HttpContext.User.Claims
+ .Where(c => c.Type == "permissions")
+ .Select(c => c.Value)
+ .ToList();
+
+ var userPermissions = userPermissionsList.Select(s => new Permission
+ {
+ Name = s.Substring(0, s.IndexOf('.')),
+ AccessLevel = s.Substring(s.IndexOf('.') + 1),
+ });
+
+ foreach (var servicePermission in servicePermissions)
+ {
+ hasPermission = userPermissions
+ .Where(up => up.Name == servicePermission.Name && up.AccessLevel == "All"
+ || up.Name == servicePermission.Name && up.AccessLevel == servicePermission.AccessLevel)
+ .Count() > 0 ? true : false;
+
+ if (hasPermission) break;
+ }
+
+ if (!hasPermission)
+ context.Result = new UnauthorizedResult();
+ }
+ catch (Exception ex)
+ {
+ context.Result = new UnauthorizedResult();
+ }
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/AccessLevelEnum.cs b/Core.Cerberos.Adapters/Common/Constants/AccessLevelEnum.cs
new file mode 100644
index 0000000..da9745e
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/AccessLevelEnum.cs
@@ -0,0 +1,32 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Specifies different access level for a permission.
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum AccessLevelEnum
+ {
+ ///
+ /// The object is accessible for reading.
+ ///
+ Read = 0,
+
+ ///
+ /// The object is accessible for writing.
+ ///
+ Write = 1,
+
+ ///
+ /// The object is accessible for all operations.
+ ///
+ All = 2
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/AzureAd.cs b/Core.Cerberos.Adapters/Common/Constants/AzureAd.cs
new file mode 100644
index 0000000..9e739b6
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/AzureAd.cs
@@ -0,0 +1,43 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for Azure Active Directory.
+ ///
+ public class AzureAd
+ {
+ ///
+ /// The ClientId parameter.
+ ///
+ public const string ClientId = "AzureAdB2C:ClientId";
+
+ ///
+ /// The TenantId parameter.
+ ///
+ public const string TenantId = "AzureAdB2C:TenantId";
+
+ ///
+ /// The ClientSecret parameter.
+ ///
+ public const string ClientSecret = "AzureAdB2C:ClientSecret";
+
+ ///
+ /// The MicrosoftOnlineUri parameter.
+ ///
+ public const string MicrosoftOnlineUri = "https://login.microsoftonline.com/";
+
+ ///
+ /// The GraphUri parameter.
+ ///
+ public const string GraphUri = "https://graph.microsoft.com/.default";
+
+ ///
+ /// The Instance parameter.
+ ///
+ public const string Instance = "AzureAdB2C:Instance";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/Claims.cs b/Core.Cerberos.Adapters/Common/Constants/Claims.cs
new file mode 100644
index 0000000..e1977aa
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Claims.cs
@@ -0,0 +1,73 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for claims used in JWT tokens.
+ ///
+ public class Claims
+ {
+ ///
+ /// Claim name for user's name.
+ ///
+ public const string Name = "name";
+
+ ///
+ /// Claim name for user's guid.
+ ///
+ public const string GUID = "guid";
+
+ ///
+ /// Claim name for user's ID.
+ ///
+ public const string Id = "id";
+
+ ///
+ /// Claim name for user's role ID.
+ ///
+ public const string Role = "role";
+
+ ///
+ /// Claim name for user's role Iidentifier.
+ ///
+ public const string RoleId = "roleId";
+
+ ///
+ /// Claim name for user's companies.
+ ///
+ public const string Companies = "companies";
+
+ ///
+ /// Claim name for user's projects.
+ ///
+ public const string Projects = "projects";
+
+ ///
+ /// Claim name for user's applications.
+ ///
+ public const string Applications = "applications";
+
+ ///
+ /// Claim name for application's modules.
+ ///
+ public const string Modules = "modules";
+
+ ///
+ /// Claim name for user's permissions.
+ ///
+ public const string Permissions = "permissions";
+
+ ///
+ /// Claim name for user's ID.
+ ///
+ public const string Email = "email";
+
+ ///
+ /// Claim name for user's role.
+ ///
+ public const string LSARoleId = "LSARoleId";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/CollectionNames.cs b/Core.Cerberos.Adapters/Common/Constants/CollectionNames.cs
new file mode 100644
index 0000000..f611d40
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/CollectionNames.cs
@@ -0,0 +1,25 @@
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ public static class CollectionNames
+ {
+ ///
+ /// The User collection name.
+ ///
+ public const string User = "Users";
+
+ ///
+ /// The Role collection name.
+ ///
+ public const string Role = "Roles";
+
+ ///
+ /// The Permission collection name.
+ ///
+ public const string Permission = "Permissions";
+
+ ///
+ /// The Module collection name.
+ ///
+ public const string Module = "Modules";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/EnvironmentVariables.cs b/Core.Cerberos.Adapters/Common/Constants/EnvironmentVariables.cs
new file mode 100644
index 0000000..d58e337
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/EnvironmentVariables.cs
@@ -0,0 +1,21 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants of the environment variables for this service.
+ ///
+ public static class EnvironmentVariables
+ {
+ ///
+ /// The stage environment vriable.
+ ///
+ public const string Stage = "ASPNETCORE_ENVIRONMENT";
+ }
+}
+
+
diff --git a/Core.Cerberos.Adapters/Common/Constants/KeyVaultConfiguration.cs b/Core.Cerberos.Adapters/Common/Constants/KeyVaultConfiguration.cs
new file mode 100644
index 0000000..c09ec85
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/KeyVaultConfiguration.cs
@@ -0,0 +1,33 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for Key Vault configuration.
+ ///
+ public class KeyVaultConfiguration
+ {
+ ///
+ /// The KeyVaultUrl parameter.
+ ///
+ public const string KeyVaultUrl = "KeyVaultConfiguration:KeyVaultUrl";
+
+ ///
+ /// The TenantId parameter.
+ ///
+ public const string TenantId = "KeyVaultConfiguration:TenantId";
+
+ ///
+ /// The ClientId parameter.
+ ///
+ public const string ClientId = "KeyVaultConfiguration:ClientId";
+
+ ///
+ /// The ClientSecretId parameter.
+ ///
+ public const string ClientSecretId = "KeyVaultConfiguration:ClientSecretId";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/MimeTypes.cs b/Core.Cerberos.Adapters/Common/Constants/MimeTypes.cs
new file mode 100644
index 0000000..d9bffc7
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/MimeTypes.cs
@@ -0,0 +1,153 @@
+// ***********************************************************************
+//
+// Axen IT
+//
+// ***********************************************************************
+
+using System.Globalization;
+
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for the mime types.
+ ///
+ public static class MimeTypes
+ {
+ ///
+ /// The application version.
+ ///
+ public const string ApplicationVersion = "1.0";
+
+ ///
+ /// The service application/json mime type.
+ ///
+ public const string ApplicationJson = "application/json";
+
+ ///
+ /// The application/pdf mime type.
+ ///
+ public const string ApplicationPdf = "application/pdf";
+
+ ///
+ /// The end index.
+ ///
+ public const int EndIndex = 5;
+
+ ///
+ /// The JPEG extension.
+ ///
+ public const string ExtensionGif = "gif";
+
+ ///
+ /// The JPEG extension.
+ ///
+ public const string ExtensionJpeg = "jpeg";
+
+ ///
+ /// The PNG extension.
+ ///
+ public const string ExtensionPng = "png";
+
+ ///
+ /// The SVG extension.
+ ///
+ public const string ExtensionSvg = "svg";
+
+ ///
+ /// The image/gif mime type.
+ ///
+ public const string ImageGif = "image/gif";
+
+ ///
+ /// The image/jpeg mime type.
+ ///
+ public const string ImageJpeg = "image/jpeg";
+
+ ///
+ /// The image/png mime type.
+ ///
+ public const string ImagePng = "image/png";
+
+ ///
+ /// The image/svg+xml mime type.
+ ///
+ public const string ImageSvg = "image/svg+xml";
+
+ ///
+ /// The identifier GIF.
+ ///
+ public const string IdentifierGif = "R0LGO";
+
+ ///
+ /// The identifier PNG.
+ ///
+ public const string IdentifierJpeg = "/9J/4";
+
+ ///
+ /// The identifier PDF.
+ ///
+ public const string IdentifierPdf = "JVBER";
+
+ ///
+ /// The identifier PNG.
+ ///
+ public const string IdentifierPng = "IVBOR";
+
+ ///
+ /// The identifier SVG.
+ ///
+ public const string IdentifierSvg = "PHN2Z";
+
+ ///
+ /// The parameter name.
+ ///
+ public const string ParameterName = "MimeType";
+
+ ///
+ /// The start index.
+ ///
+ public const int StartIndex = 0;
+
+ ///
+ /// The mime type dictionary.
+ ///
+ public static readonly Dictionary Dictionary = new Dictionary
+ {
+ { IdentifierJpeg, ImageJpeg },
+ { IdentifierPng, ImagePng },
+ { IdentifierGif, ImageGif },
+ { IdentifierSvg, ImageSvg },
+ };
+
+ ///
+ /// The mime type dictionary.
+ ///
+ public static readonly Dictionary DictionaryExtension = new Dictionary
+ {
+ { IdentifierJpeg, ExtensionJpeg },
+ { IdentifierPng, ExtensionPng },
+ { IdentifierGif, ExtensionGif },
+ { IdentifierSvg, ExtensionSvg },
+ };
+
+ ///
+ /// Gets the mime type.
+ ///
+ /// The content with mime type identifier, substring 0, 5 from content.
+ /// A representing the value.
+ public static string GetMimeType(this string content)
+ {
+ return Dictionary.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
+ }
+
+ ///
+ /// Gets the extension.
+ ///
+ /// The mime type identifier, substring 0, 5 from content.
+ /// A representing the value.
+ public static string GetExtension(this string content)
+ {
+ return DictionaryExtension.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/Routes.cs b/Core.Cerberos.Adapters/Common/Constants/Routes.cs
new file mode 100644
index 0000000..440aac9
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Routes.cs
@@ -0,0 +1,114 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants of the routes of this service.
+ ///
+ public static class Routes
+ {
+ ///
+ /// The User route.
+ ///
+ public const string User = "users";
+
+ ///
+ /// The Register User route.
+ ///
+ public const string Register = "{sendInvitation}/send-invitation/register";
+
+ ///
+ /// The identifier route.
+ ///
+ public const string Id = "{id}";
+
+ ///
+ /// The Authentication route.
+ ///
+ public const string Authentication = "api/v1/authentication";
+
+ ///
+ /// The LogIn route.
+ ///
+ public const string LogIn = "{email}/login";
+
+ ///
+ /// The LogOut route.
+ ///
+ public const string LogOut = "{email}/logout";
+
+ ///
+ /// The Generate Token route.
+ ///
+ public const string GenerateToken = "GenerateToken";
+
+ ///
+ /// The refresh token route.
+ ///
+ public const string RefreshToken = "RefreshToken";
+
+ ///
+ /// The InviteUser route.
+ ///
+ public const string InviteUser = "invite-user";
+
+ ///
+ /// The role identifier route.
+ ///
+ public const string RoleId = "role/{roleId}";
+
+ ///
+ /// The GetPermissionList route.
+ ///
+ public const string GetPermissionList = "GetPermissionList";
+
+ ///
+ /// The GetModuleList route.
+ ///
+ public const string GetModuleList = "GetModuleList";
+
+ ///
+ /// The ChangeStatus route.
+ ///
+ public const string ChangeStatus = "{id}/{newStatus}/ChangeStatus";
+
+ ///
+ /// The AddCompany route.
+ ///
+ public const string AddCompany = "{userId}/Companies/{companyId}/Add";
+
+ ///
+ /// The RemoveCompany route.
+ ///
+ public const string RemoveCompany = "{userId}/Companies/{companyId}/Remove";
+
+ ///
+ /// The AddProject route.
+ ///
+ public const string AddProject = "{userId}/Projects/{projectId}/Add";
+
+ ///
+ /// The RemoveProject route.
+ ///
+ public const string RemoveProject = "{userId}/Projects/{projectId}/Remove";
+
+ ///
+ /// The AddApplication route.
+ ///
+ public const string AddApplication = "{roleId}/{application}/AddApplication";
+
+ ///
+ /// The RemoveApplication route.
+ ///
+ public const string RemoveApplication = "{roleId}/{application}/RemoveApplication";
+
+ ///
+ /// The email route.
+ ///
+ public const string Email = "{email}/GetByEmail";
+ }
+}
\ No newline at end of file
diff --git a/Core.Cerberos.Adapters/Common/Constants/Schemes.cs b/Core.Cerberos.Adapters/Common/Constants/Schemes.cs
new file mode 100644
index 0000000..67e77e5
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Schemes.cs
@@ -0,0 +1,18 @@
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for schemes.
+ ///
+ public class Schemes
+ {
+ ///
+ /// The heath scheme.
+ ///
+ public const string HeathScheme = "HeathScheme";
+
+ ///
+ /// The azure scheme.
+ ///
+ public const string AzureScheme = "AzureScheme";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/Secrets.cs b/Core.Cerberos.Adapters/Common/Constants/Secrets.cs
new file mode 100644
index 0000000..a3fc7c7
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Secrets.cs
@@ -0,0 +1,59 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+namespace Core.Cerberos.Adapters.Common.Constants
+{
+ ///
+ /// Constants for secrets in azure key vault.
+ ///
+ public class Secrets
+ {
+ ///
+ /// The MongoDBName parameter.
+ ///
+ public const string MongoDBName = "MongoDBName";
+
+ ///
+ /// The MongoDBConnection parameter.
+ ///
+ public const string MongoDBConnection = "MongoDBConnection";
+
+ ///
+ /// The Issuer parameter for JWT settings.
+ ///
+ public const string Issuer = "Issuer";
+
+ ///
+ /// The Audience parameter for JWT settings.
+ ///
+ public const string Audience = "Audience";
+
+ ///
+ /// The TokenExpirationInMinutes parameter for JWT settings.
+ ///
+ public const string TokenExpirationInMinutes = "TokenExpirationInMinutes";
+
+ ///
+ /// The TokenExpirationInHours parameter for JWT settings.
+ ///
+ public const string TokenExpirationInHours = "TokenExpirationInHours";
+
+ ///
+ /// The IssuerSigningKey parameter for JWT settings.
+ ///
+ public const string IssuerSigningKey = "IssuerSigningKey";
+
+ public const string AzureADInstance = "B2C:InstanceUri";
+ public const string AzureADTenantId = "B2C:TenantId";
+ public const string AzureADClientId = "B2C:ClientId";
+ public const string AzureADClientSecret = "B2C:ClientSecret";
+ public const string HeathCerberosAppAuthorizationUrl = "Swagger:AuthorizationUri";
+ public const string HeathCerberosAppTokenUrl = "Swagger:TokenUri";
+ public const string HeathCerberosAppClientId = "Swagger:ClientId";
+ public const string HeathCerberosAppScope = "Swagger:Scope";
+ public const string PrivateKey = "B2C:JwtIssuerOptions:TokenPrivateKey";
+ public const string PublicKey = "B2C:JwtIssuerOptions:TokenPublicKey";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Enums/ApplicationsEnum.cs b/Core.Cerberos.Adapters/Common/Enums/ApplicationsEnum.cs
new file mode 100644
index 0000000..c51bf6f
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Enums/ApplicationsEnum.cs
@@ -0,0 +1,42 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters.Common.Enums
+{
+ ///
+ /// Defines the applications associated with the role.
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum ApplicationsEnum
+ {
+ ///
+ /// LSA Web Portal application.
+ ///
+ LSAWebPortal = 0,
+
+ ///
+ /// Customer DashBoard application.
+ ///
+ CustomerDashboard = 1,
+
+ ///
+ /// Discover application.
+ ///
+ Discover = 2,
+
+ ///
+ /// LSA Mobile application.
+ ///
+ LSAMobile = 3,
+
+ ///
+ /// BluePrint application.
+ ///
+ BluePrint = 4,
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Enums/StatusEnum.cs b/Core.Cerberos.Adapters/Common/Enums/StatusEnum.cs
new file mode 100644
index 0000000..213cad4
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Enums/StatusEnum.cs
@@ -0,0 +1,32 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters.Common.Enums
+{
+ ///
+ /// Defines the status of an entity.
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum StatusEnum
+ {
+ ///
+ /// Indicates the entity is active.
+ ///
+ Active = 0,
+
+ ///
+ /// Indicates the entity is inactive.
+ ///
+ Inactive = 1,
+
+ ///
+ /// Indicates the entity is deleted.
+ ///
+ Deleted = 2
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/SchemaFilters/EnumArrayJsonConverter.cs b/Core.Cerberos.Adapters/Common/SchemaFilters/EnumArrayJsonConverter.cs
new file mode 100644
index 0000000..686f088
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/SchemaFilters/EnumArrayJsonConverter.cs
@@ -0,0 +1,17 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+public class EnumArrayJsonConverter : JsonConverter where T : Enum
+{
+ public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var values = JsonSerializer.Deserialize(ref reader, options);
+ return Array.ConvertAll(values, value => (T)Enum.Parse(typeof(T), value));
+ }
+
+ public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
+ {
+ var stringValues = Array.ConvertAll(value, v => v.ToString());
+ JsonSerializer.Serialize(writer, stringValues, options);
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/SchemaFilters/EnumSchemaFilter.cs b/Core.Cerberos.Adapters/Common/SchemaFilters/EnumSchemaFilter.cs
new file mode 100644
index 0000000..cdc9480
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/SchemaFilters/EnumSchemaFilter.cs
@@ -0,0 +1,31 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Microsoft.OpenApi.Any;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+///
+/// Applies enumeration schema to OpenAPI schema definitions.
+///
+public class EnumSchemaFilter : ISchemaFilter
+{
+ ///
+ /// Applies the schema filter to an OpenAPI schema.
+ ///
+ /// The OpenAPI schema to apply the filter to.
+ /// The context information for the schema filter.
+ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ {
+ if (context.Type.IsEnum)
+ {
+ schema.Enum.Clear();
+ Enum.GetNames(context.Type)
+ .ToList()
+ .ForEach(name => schema.Enum.Add(new OpenApiString(name)));
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Contracts/ITokenProvider.cs b/Core.Cerberos.Adapters/Contracts/ITokenProvider.cs
new file mode 100644
index 0000000..2eaf533
--- /dev/null
+++ b/Core.Cerberos.Adapters/Contracts/ITokenProvider.cs
@@ -0,0 +1,19 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+namespace Core.Cerberos.Adapters.Contracts
+{
+ ///
+ /// Interface for token provider.
+ ///
+ public interface ITokenProvider
+ {
+ ///
+ /// Get token from headers.
+ ///
+ string GetToken();
+ }
+}
diff --git a/Core.Cerberos.Adapters/Contracts/ITokenService.cs b/Core.Cerberos.Adapters/Contracts/ITokenService.cs
new file mode 100644
index 0000000..b68609a
--- /dev/null
+++ b/Core.Cerberos.Adapters/Contracts/ITokenService.cs
@@ -0,0 +1,32 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Core.Cerberos.Adapters.Contracts
+{
+ ///
+ /// Interface for authenticacion service.
+ ///
+ public interface ITokenService
+ {
+ ///
+ /// Refreshes the access token.
+ ///
+ string GenerateAccessToken(TokenAdapter adapter);
+
+ ///
+ /// Refreshes the access token.
+ ///
+ IActionResult RefreshAccessToken(HttpContext context, TokenAdapter adapter);
+
+ ///
+ /// Extracts the user email claim from the http context.
+ ///
+ string GetEmailClaim(HttpContext httpContext);
+ }
+}
diff --git a/Core.Cerberos.Adapters/Core.Cerberos.Adapters.csproj b/Core.Cerberos.Adapters/Core.Cerberos.Adapters.csproj
new file mode 100644
index 0000000..5bcf6c6
--- /dev/null
+++ b/Core.Cerberos.Adapters/Core.Cerberos.Adapters.csproj
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core.Cerberos.Adapters/Extensions/AuthenticationExtension.cs b/Core.Cerberos.Adapters/Extensions/AuthenticationExtension.cs
new file mode 100644
index 0000000..315c205
--- /dev/null
+++ b/Core.Cerberos.Adapters/Extensions/AuthenticationExtension.cs
@@ -0,0 +1,97 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Common.Constants;
+using Core.Cerberos.Adapters.Contracts;
+using Core.Cerberos.Adapters.Handlers;
+using Core.Cerberos.Adapters.Options;
+using Core.Cerberos.Adapters.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Identity.Web;
+using Microsoft.IdentityModel.Tokens;
+using System.Security.Cryptography;
+
+namespace Core.Cerberos.Adapters.Extensions
+{
+ ///
+ /// Extension methods for configuring authentication with various Azure AD setups.
+ ///
+ public static class AuthenticationExtension
+ {
+ ///
+ /// Configures authentication using Azure AD for an API that requires downstream API access.
+ ///
+ /// The to add the services to.
+ /// The containing Azure AD configuration settings.
+ public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration, AuthSettings authSettings)
+ {
+ var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
+
+ var azureAdInMemorySettings = new Dictionary
+ {
+ { "AzureAdB2C:Instance", authSettings.AzureADInstance ?? string.Empty },
+ { "AzureAdB2C:TenantId", authSettings.AzureADTenantId ?? string.Empty },
+ { "AzureAdB2C:ClientId", authSettings.AzureADClientId ?? string.Empty },
+ { "AzureAdB2C:ClientSecret", authSettings.AzureADClientSecret ?? string.Empty }
+ };
+
+ var configurationBuilder = new ConfigurationBuilder()
+ .AddConfiguration(configuration)
+ .AddInMemoryCollection(azureAdInMemorySettings);
+
+ var combinedConfiguration = configurationBuilder.Build();
+
+ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddMicrosoftIdentityWebApi(combinedConfiguration.GetSection("AzureAdB2C"), Schemes.AzureScheme)
+ .EnableTokenAcquisitionToCallDownstreamApi()
+ .AddMicrosoftGraph(configuration.GetSection("MicrosoftGraph"))
+ .AddInMemoryTokenCaches();
+
+ var rsa = RSA.Create();
+ rsa.ImportFromPem(authSettings.PrivateKey?.ToCharArray());
+ var rsaPrivateKey = new RsaSecurityKey(rsa);
+
+ var rsaPublic = RSA.Create();
+ rsaPublic.ImportFromPem(authSettings.PublicKey?.ToCharArray());
+ var rsaPublicKey = new RsaSecurityKey(rsaPublic);
+
+ var jwtAppSettingOptions = configuration.GetSection("B2C:JwtIssuerOptions");
+ var jwtIssuerOptions = jwtAppSettingOptions.Get();
+
+ if (string.IsNullOrEmpty(jwtIssuerOptions?.Issuer) || string.IsNullOrEmpty(jwtIssuerOptions.Audience))
+ throw new InvalidOperationException("JwtIssuerOptions are not configured correctly.");
+
+ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(Schemes.HeathScheme, x =>
+ {
+ x.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = jwtIssuerOptions?.Issuer,
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidAudience = jwtIssuerOptions?.Audience,
+ IssuerSigningKey = rsaPublicKey
+ };
+ });
+
+ services.Configure(options =>
+ {
+ options.Issuer = jwtIssuerOptions?.Issuer;
+ options.Audience = jwtIssuerOptions?.Audience;
+ options.SigningCredentials = new SigningCredentials(rsaPrivateKey, SecurityAlgorithms.RsaSha256);
+ });
+
+ services.AddSingleton(jwtAppSettingOptions);
+ services.AddTransient();
+ services.AddTransient();
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Extensions/SwaggerExtensions.cs b/Core.Cerberos.Adapters/Extensions/SwaggerExtensions.cs
new file mode 100644
index 0000000..ec311bb
--- /dev/null
+++ b/Core.Cerberos.Adapters/Extensions/SwaggerExtensions.cs
@@ -0,0 +1,193 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Asp.Versioning.ApiExplorer;
+using Core.Cerberos.Adapters.Common.Constants;
+using Core.Cerberos.Adapters.Extensions;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.OpenApi.Any;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Swashbuckle.AspNetCore.SwaggerUI;
+
+namespace Core.Cerberos.Adapters.Extensions
+{
+ ///
+ /// Extension methods for configuring Swagger documentation and UI.
+ ///
+ public static class SwaggerExtensions
+ {
+ private static readonly string? environment = Environment.GetEnvironmentVariable(EnvironmentVariables.Stage);
+ ///
+ /// Adds Swagger services to the specified .
+ ///
+ /// The to add the services to.
+ public static void AddSwagger(this IServiceCollection services, IConfiguration configuration, string DocumentationFile, AuthSettings authSettings)
+ {
+ services.AddEndpointsApiExplorer();
+ services.AddSwaggerGen(configuration, DocumentationFile, authSettings);
+ services.AddTransient, ConfigureSwaggerOptions>();
+ }
+
+ ///
+ /// Configures Swagger generation with OAuth2 security and XML comments.
+ ///
+ /// The to add the services to.
+ /// The containing Swagger and OAuth2 configuration settings.
+ public static void AddSwaggerGen(this IServiceCollection services, IConfiguration configuration, string DocumentationFile, AuthSettings authSettings)
+ {
+ services.AddSwaggerGen(c =>
+ {
+ c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
+ {
+ Description = "OAuth2.0 Authorization Code flow",
+ Name = "oauth2.0",
+ Type = SecuritySchemeType.OAuth2,
+ Flows = new OpenApiOAuthFlows
+ {
+ AuthorizationCode = new OpenApiOAuthFlow
+ {
+ AuthorizationUrl = new Uri(authSettings.HeathCerberosAppAuthorizationUrl ?? string.Empty),
+ TokenUrl = new Uri(authSettings.HeathCerberosAppTokenUrl ?? string.Empty),
+ Scopes = new Dictionary
+ {
+ { authSettings.HeathCerberosAppScope ?? string.Empty, "Access API as User" }
+ }
+ }
+ }
+ });
+
+ c.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
+ },
+ new[] { authSettings.HeathCerberosAppScope }
+ }
+ });
+
+ c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
+ {
+ Description = "JWT Authorization header using the Bearer scheme",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
+ Type = SecuritySchemeType.Http,
+ Scheme = "bearer",
+ BearerFormat = "JWT"
+ });
+
+ c.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ }
+ },
+ Array.Empty()
+ }
+ });
+
+ var filePath = Path.Combine(AppContext.BaseDirectory, DocumentationFile);
+ c.IncludeXmlComments(filePath);
+ c.SchemaFilter();
+ });
+ }
+
+ ///
+ /// Configures Swagger and Swagger UI for the application.
+ ///
+ /// The instance.
+ /// The containing Swagger configuration settings.
+ public static void ConfigureSwagger(this WebApplication app, IConfiguration configuration)
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI(options =>
+ {
+ foreach (var version in app.DescribeApiVersions().Select(version => version.GroupName))
+ options.SwaggerEndpoint($"/swagger/{version}/swagger.json", version);
+
+ options.DisplayRequestDuration();
+ options.EnableTryItOutByDefault();
+ options.DocExpansion(DocExpansion.None);
+ });
+ }
+
+ ///
+ /// Configures Swagger UI for the application with OAuth2 settings.
+ ///
+ /// The instance.
+ /// The containing Swagger UI and OAuth2 configuration settings.
+ public static void UseSwaggerUI(this WebApplication app, IConfiguration configuration, AuthSettings authSettings)
+ {
+ app.UseSwaggerUI(options =>
+ {
+ options.SwaggerEndpoint("/swagger/v1/swagger.json", "Custom Auth API with Azure AD v1");
+ options.OAuthClientId(authSettings.HeathCerberosAppClientId);
+ options.OAuthUsePkce();
+ options.OAuthScopeSeparator(" ");
+ });
+ }
+
+ ///
+ /// Adds API versioning and API explorer to the application.
+ ///
+ /// The to add the services to.
+ /// The modified instance.
+ public static IServiceCollection AddVersioning(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddApiVersioning(options => options.ReportApiVersions = true)
+ .AddApiExplorer(options =>
+ {
+ options.GroupNameFormat = "'v'VVV";
+ options.SubstituteApiVersionInUrl = true;
+ });
+
+ return services;
+ }
+ }
+
+ ///
+ /// Configures Swagger generation options.
+ ///
+ public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions
+ {
+ ///
+ /// Configures SwaggerGen options.
+ ///
+ /// The SwaggerGen options to configure.
+ public void Configure(SwaggerGenOptions options)
+ {
+ foreach (var description in provider.ApiVersionDescriptions)
+ {
+ options.SwaggerDoc(description.GroupName, new OpenApiInfo
+ {
+ Title = AppDomain.CurrentDomain.FriendlyName,
+ Version = description.ApiVersion.ToString()
+ });
+ }
+
+ // Map DateOnly type to Swagger schema
+ options.MapType(() => new OpenApiSchema
+ {
+ Type = "string",
+ Format = "date",
+ Example = new OpenApiString(DateOnly.MinValue.ToString())
+ });
+
+ // Customize schema IDs for Swagger
+ options.CustomSchemaIds(type => type.ToString().Replace("+", "."));
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Extensions/TelemetryExtensions.cs b/Core.Cerberos.Adapters/Extensions/TelemetryExtensions.cs
new file mode 100644
index 0000000..816fa0c
--- /dev/null
+++ b/Core.Cerberos.Adapters/Extensions/TelemetryExtensions.cs
@@ -0,0 +1,22 @@
+using Microsoft.Extensions.DependencyInjection;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+
+namespace Core.Cerberos.Adapters.Extensions
+{
+ public static class TelemetryExtensions
+ {
+ public static void AddTelemetry(this IServiceCollection services)
+ {
+ // Add OpenTelemetry Tracing
+ services.AddOpenTelemetry()
+ .ConfigureResource(resource => resource.AddService("lsa.dashboard.bff.api"))
+ .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation().AddConsoleExporter())
+ .WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation().AddConsoleExporter()).
+ WithLogging(logs => logs.AddConsoleExporter());
+
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Extensions/TrackingMechanismExtension.cs b/Core.Cerberos.Adapters/Extensions/TrackingMechanismExtension.cs
new file mode 100644
index 0000000..9dc847f
--- /dev/null
+++ b/Core.Cerberos.Adapters/Extensions/TrackingMechanismExtension.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Core.Cerberos.Adapters.Extensions
+{
+ public sealed class TrackingMechanismExtension : DelegatingHandler
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public TrackingMechanismExtension(IHttpContextAccessor httpContextAccessor)
+ {
+ _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (_httpContextAccessor.HttpContext.Items.TryGetValue("TrackingId", out var trackingId))
+ {
+ request.Headers.Add("TrackingId", trackingId.ToString());
+ }
+ return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Handlers/Adapters/PermissionsAuthorizationAdapter.cs b/Core.Cerberos.Adapters/Handlers/Adapters/PermissionsAuthorizationAdapter.cs
new file mode 100644
index 0000000..82ea008
--- /dev/null
+++ b/Core.Cerberos.Adapters/Handlers/Adapters/PermissionsAuthorizationAdapter.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Core.Cerberos.Adapters.Handlers.Adapters
+{
+ public class PermissionsAuthorizationAdapter : IAuthorizationRequirement
+ {
+ public PermissionsAuthorizationAdapter(string[] permission)
+ {
+ Permission = permission;
+ }
+ public string[] Permission { get; set; }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Handlers/AuthenticatedHttpClientHandler.cs b/Core.Cerberos.Adapters/Handlers/AuthenticatedHttpClientHandler.cs
new file mode 100644
index 0000000..dcf7231
--- /dev/null
+++ b/Core.Cerberos.Adapters/Handlers/AuthenticatedHttpClientHandler.cs
@@ -0,0 +1,29 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Contracts;
+
+namespace Core.Cerberos.Adapters.Handlers
+{
+ ///
+ /// Class to inject the token in all requests.
+ ///
+ public class AuthenticatedHttpClientHandler(ITokenProvider tokenProvider) : DelegatingHandler
+ {
+ private readonly ITokenProvider _tokenProvider = tokenProvider;
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var token = _tokenProvider.GetToken();
+ if (!string.IsNullOrEmpty(token))
+ {
+ request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
+ }
+
+ return await base.SendAsync(request, cancellationToken);
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Handlers/PermissionsAuthorizationHandler.cs b/Core.Cerberos.Adapters/Handlers/PermissionsAuthorizationHandler.cs
new file mode 100644
index 0000000..cf15512
--- /dev/null
+++ b/Core.Cerberos.Adapters/Handlers/PermissionsAuthorizationHandler.cs
@@ -0,0 +1,18 @@
+using Core.Cerberos.Adapters.Handlers.Adapters;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Core.Cerberos.Adapters.Handlers
+{
+ public class PermissionsAuthorizationHandler : AuthorizationHandler
+ {
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionsAuthorizationAdapter requirement)
+ {
+ if (context.User.Claims.Any(x => x.Type == "LSARoleId" && requirement.Permission.Contains(x.Value)))
+ {
+ context.Succeed(requirement);
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Helpers/AuthHelper.cs b/Core.Cerberos.Adapters/Helpers/AuthHelper.cs
new file mode 100644
index 0000000..f6ca9fd
--- /dev/null
+++ b/Core.Cerberos.Adapters/Helpers/AuthHelper.cs
@@ -0,0 +1,52 @@
+using Azure.Identity;
+using Core.Cerberos.Adapters.Common.Constants;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.AzureAppConfiguration;
+using Microsoft.Extensions.Logging;
+
+namespace Core.Cerberos.Adapters.Helpers
+{
+ public static class AuthHelper
+ {
+ private static readonly ILogger logger = LoggerFactory.Create(builder =>
+ {
+ builder.AddConsole();
+ }).CreateLogger("AuthHelper");
+
+
+ public static AuthSettings GetAuthSettings(WebApplicationBuilder builder, string appConfigLabel)
+ {
+ builder.Configuration.AddAzureAppConfiguration(options =>
+ {
+ var endpoint = builder.Configuration.GetSection("Endpoints:AppConfigurationURI").Value;
+
+ if (string.IsNullOrEmpty(endpoint))
+ throw new ArgumentException("The app configuration is missing");
+
+ options.Connect(new Uri(endpoint), new DefaultAzureCredential())
+ .Select(KeyFilter.Any, "cerberos_common")
+ .Select(KeyFilter.Any, appConfigLabel);
+
+ options.ConfigureKeyVault(keyVaultOptions =>
+ {
+ keyVaultOptions.SetCredential(new DefaultAzureCredential());
+ });
+ });
+
+ return new AuthSettings
+ {
+ AzureADInstance = builder.Configuration.GetSection(Secrets.AzureADInstance).Value,
+ AzureADTenantId = builder.Configuration.GetSection(Secrets.AzureADTenantId).Value,
+ AzureADClientId = builder.Configuration.GetSection(Secrets.AzureADClientId).Value,
+ AzureADClientSecret = builder.Configuration.GetSection(Secrets.AzureADClientSecret).Value,
+ HeathCerberosAppAuthorizationUrl = builder.Configuration.GetSection(Secrets.HeathCerberosAppAuthorizationUrl).Value,
+ HeathCerberosAppTokenUrl = builder.Configuration.GetSection(Secrets.HeathCerberosAppTokenUrl).Value,
+ HeathCerberosAppClientId = builder.Configuration.GetSection(Secrets.HeathCerberosAppClientId).Value,
+ HeathCerberosAppScope = builder.Configuration.GetSection(Secrets.HeathCerberosAppScope).Value,
+ PrivateKey = builder.Configuration.GetSection(Secrets.PrivateKey).Value,
+ PublicKey = builder.Configuration.GetSection(Secrets.PublicKey).Value,
+ };
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Helpers/RsaHelper.cs b/Core.Cerberos.Adapters/Helpers/RsaHelper.cs
new file mode 100644
index 0000000..1cd296c
--- /dev/null
+++ b/Core.Cerberos.Adapters/Helpers/RsaHelper.cs
@@ -0,0 +1,94 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Security;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Core.Cerberos.Adapters.Helpers
+{
+ ///
+ /// Handles all methods related to RSA encryption"/>.
+ ///
+ public class RsaHelper
+ {
+ private readonly RSACryptoServiceProvider _privateKey;
+ private readonly RSACryptoServiceProvider _publicKey;
+ private readonly string keysFolder = "Keys\\";
+ private readonly string exeDirectory = AppContext.BaseDirectory;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public RsaHelper()
+ {
+ exeDirectory = exeDirectory + keysFolder;
+
+ _publicKey = GetPublicKeyFromPemFile();
+ _privateKey = GetPrivateKeyFromPemFile();
+ }
+
+ ///
+ /// Encrypts a text using RSA algorithm.
+ ///
+ /// The text to be encrypted.
+ /// The encrypted text.
+ public string Encrypt(string text)
+ {
+ byte[] dataBytes = Encoding.UTF8.GetBytes(text);
+ var encryptedBytes = _publicKey.Encrypt(Encoding.UTF8.GetBytes(text), true);
+ return Convert.ToBase64String(encryptedBytes);
+ }
+
+ ///
+ /// Decrypts a text using RSA algorithm.
+ ///
+ /// The encrypted text to be decrypted.
+ /// The decrypted text.
+ public string Decrypt(string encrypted)
+ {
+ var decryptedBytes = _privateKey.Decrypt(Convert.FromBase64String(encrypted), true);
+ return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
+ }
+
+ ///
+ ///Obtains the private key from a file.
+ ///
+ /// The private key.
+ private RSACryptoServiceProvider GetPrivateKeyFromPemFile()
+ {
+ using (TextReader privateKeyTextReader = new StringReader(File.ReadAllText(Path.Combine(exeDirectory, "HeathPrivateKey.pem"))))
+ {
+ AsymmetricCipherKeyPair readKeyPair = (AsymmetricCipherKeyPair)new PemReader(privateKeyTextReader).ReadObject();
+
+ RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)readKeyPair.Private);
+ RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
+ csp.ImportParameters(rsaParams);
+ return csp;
+ }
+ }
+
+ ///
+ ///Obtains the public key from a file.
+ ///
+ /// The public key.
+ public RSACryptoServiceProvider GetPublicKeyFromPemFile()
+ {
+ using (TextReader publicKeyTextReader = new StringReader(File.ReadAllText(Path.Combine(exeDirectory, "HeathPublicKey.pem"))))
+ {
+ RsaKeyParameters publicKeyParam = (RsaKeyParameters)new PemReader(publicKeyTextReader).ReadObject();
+
+ RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKeyParam);
+
+ RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
+ csp.ImportParameters(rsaParams);
+ return csp;
+ }
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Options/JwtIssuerOptions.cs b/Core.Cerberos.Adapters/Options/JwtIssuerOptions.cs
new file mode 100644
index 0000000..d103d15
--- /dev/null
+++ b/Core.Cerberos.Adapters/Options/JwtIssuerOptions.cs
@@ -0,0 +1,60 @@
+using Microsoft.IdentityModel.Tokens;
+
+namespace Core.Cerberos.Adapters.Options
+{
+ ///
+ /// JWT token Issuer options (used for JWT Factory)
+ ///
+
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public class JwtIssuerOptions
+ {
+ ///
+ /// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
+ ///
+ public string? Issuer { get; set; }
+
+ ///
+ /// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT.
+ ///
+ public string? Subject { get; set; }
+
+ ///
+ /// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for.
+ ///
+ public string? Audience { get; set; }
+
+ ///
+ /// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
+ ///
+ public DateTime Expiration => IssuedAt.Add(ValidFor);
+
+ ///
+ /// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.
+ ///
+ public DateTime NotBefore => DateTime.UtcNow;
+
+ ///
+ /// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued.
+ ///
+ public DateTime IssuedAt => DateTime.UtcNow;
+
+ ///
+ /// Set the timespan the token will be valid for (default is 120 min)
+ ///
+ public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(525601);
+
+
+
+ ///
+ /// "jti" (JWT ID) Claim (default ID is a GUID)
+ ///
+ public Func> JtiGenerator =>
+ () => Task.FromResult(Guid.NewGuid().ToString());
+
+ ///
+ /// The signing key to use when generating tokens.
+ ///
+ public SigningCredentials? SigningCredentials { get; set; }
+ }
+}
diff --git a/Core.Cerberos.Adapters/Services/TokenService.cs b/Core.Cerberos.Adapters/Services/TokenService.cs
new file mode 100644
index 0000000..937fb4c
--- /dev/null
+++ b/Core.Cerberos.Adapters/Services/TokenService.cs
@@ -0,0 +1,146 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+using Core.Cerberos.Adapters.Common.Constants;
+using Core.Cerberos.Adapters.Contracts;
+using Core.Cerberos.Adapters.Options;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.Data;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text.Json;
+
+namespace Core.Cerberos.Adapters.Services
+{
+ ///
+ /// Service responsible for manage authenticacion.
+ ///
+ public class TokenService : ITokenService
+ {
+ private readonly JwtSecurityTokenHandler tokenHandler;
+ private readonly IConfiguration configuration;
+ private readonly JwtIssuerOptions jwtOptions;
+ private readonly JsonSerializerOptions jsonOptions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TokenService(
+ IConfiguration configuration,
+ IOptions jwtOptions
+ )
+ {
+ tokenHandler = new JwtSecurityTokenHandler();
+ this.configuration = configuration;
+ this.jwtOptions = jwtOptions.Value;
+ jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = false
+ };
+ }
+
+ ///
+ /// Refreshes the token.
+ ///
+ public IActionResult RefreshAccessToken(HttpContext httpContext, TokenAdapter tokenAdapter)
+ {
+ var tokenString = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+
+ if (tokenString is not null)
+ {
+ var oldToken = tokenHandler.ReadJwtToken(tokenString);
+
+ var tokenExpiration = oldToken.Claims.FirstOrDefault(c => c.Type == "exp")?.Value;
+
+ var difference = ValidateTokenExpiration(tokenExpiration ?? "");
+
+ if (difference.Value.TotalMinutes <= 5)
+
+ return new OkObjectResult(GenerateAccessToken(tokenAdapter));
+ }
+
+ return new BadRequestObjectResult("The token could not be refreshed");
+ }
+
+ ///
+ /// Generates a JWT token for the provided user data.
+ ///
+ /// The user data.
+ /// The user DTO with the generated token.
+ public string GenerateAccessToken(TokenAdapter adapter)
+ {
+
+
+ var hours = 1;
+ var minutes = 0;
+ var expires = DateTime.UtcNow
+ .AddHours(hours)
+ .AddMinutes(minutes);
+
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new Claim[]
+ {
+
+ new Claim(Claims.Name, adapter?.User?.DisplayName ?? string.Empty),
+ new Claim(Claims.GUID, adapter?.User?.Guid ?? string.Empty),
+ new Claim(Claims.Email, adapter?.User?.Email ?? string.Empty),
+ new Claim(Claims.Role, adapter?.Role?.Name ?? string.Empty),
+ new Claim(Claims.RoleId, adapter?.Role?.Id ?? string.Empty),
+ new Claim(Claims.Applications, JsonSerializer.Serialize(adapter?.Role?.Applications), JsonClaimValueTypes.JsonArray),
+ new Claim(Claims.Modules, JsonSerializer.Serialize(adapter?.Modules?.Select(m => new { m.Name, m.Application, m.Route, m.Icon, m.Order }), jsonOptions), JsonClaimValueTypes.JsonArray),
+ new Claim(Claims.Companies, JsonSerializer.Serialize(adapter?.User?.Companies), JsonClaimValueTypes.JsonArray),
+ new Claim(Claims.Projects, JsonSerializer.Serialize(adapter?.User?.Projects), JsonClaimValueTypes.JsonArray),
+ new Claim(Claims.Permissions, JsonSerializer.Serialize(adapter?.Permissions?.Select(p => $"{p.Name}.{p.AccessLevel}".Replace(" ", "")).ToArray()), JsonClaimValueTypes.JsonArray),
+ }),
+
+ Expires = expires,
+ Issuer = jwtOptions.Issuer,
+ Audience = jwtOptions.Audience,
+ SigningCredentials = jwtOptions.SigningCredentials
+ };
+
+ var token = tokenHandler.CreateEncodedJwt(tokenDescriptor);
+
+ return token;
+ }
+
+ public ActionResult ValidateTokenExpiration(string tokenExpiration)
+ {
+ long unixTimestamp = long.Parse(tokenExpiration ?? "0");
+ DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
+ DateTime dateTimeExpiration = dateTimeOffset.UtcDateTime;
+
+ var difference = dateTimeExpiration - DateTime.UtcNow;
+
+ if (difference.TotalMinutes <= 0)
+
+ return new BadRequestObjectResult("Expired token");
+
+ else return difference;
+ }
+
+ ///
+ /// Extracts the user email claim from the http context.
+ ///
+ public string GetEmailClaim(HttpContext httpContext)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+
+ var tokenString = httpContext.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last();
+ var token = tokenHandler.ReadJwtToken(tokenString);
+ var email = !string.IsNullOrEmpty(token.Claims.FirstOrDefault(c => c.Type == "email")?.Value)
+ ? token.Claims.FirstOrDefault(c => c.Type == "email")?.Value
+ : token.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value;
+
+ return (email is not null) ? email : "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core.Cerberos.Adapters/Settings/AuthSettings.cs b/Core.Cerberos.Adapters/Settings/AuthSettings.cs
new file mode 100644
index 0000000..5becd38
--- /dev/null
+++ b/Core.Cerberos.Adapters/Settings/AuthSettings.cs
@@ -0,0 +1,25 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+public class AuthSettings
+{
+ // Azure AD Settings
+ public string? AzureADInstance { get; set; }
+ public string? AzureADTenantId { get; set; }
+ public string? AzureADClientId { get; set; }
+ public string? AzureADClientSecret { get; set; }
+
+ // Heath Cerberos App Settings
+ public string? HeathCerberosAppAuthorizationUrl { get; set; }
+ public string? HeathCerberosAppTokenUrl { get; set; }
+ public string? HeathCerberosAppClientId { get; set; }
+ public string? HeathCerberosAppScope { get; set; }
+
+ // Token Keys
+ public string? PrivateKey { get; set; }
+ public string? PublicKey { get; set; }
+}
+
diff --git a/Core.Cerberos.Adapters/TokenProvider/HttpContextTokenProvider.cs b/Core.Cerberos.Adapters/TokenProvider/HttpContextTokenProvider.cs
new file mode 100644
index 0000000..1ccece5
--- /dev/null
+++ b/Core.Cerberos.Adapters/TokenProvider/HttpContextTokenProvider.cs
@@ -0,0 +1,32 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using Core.Cerberos.Adapters.Contracts;
+using Microsoft.AspNetCore.Http;
+
+namespace Core.Cerberos.Adapters.TokenProvider
+{
+ ///
+ /// Class to return the access token to controllers.
+ ///
+ public class HttpContextTokenProvider : ITokenProvider
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public HttpContextTokenProvider(IHttpContextAccessor httpContextAccessor)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ ///
+ /// Get token from headers.
+ ///
+ public string GetToken()
+ {
+ return _httpContextAccessor.HttpContext?.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
+ }
+ }
+}
diff --git a/Core.Cerberos.Adapters/UserExistenceAdapter.cs b/Core.Cerberos.Adapters/UserExistenceAdapter.cs
new file mode 100644
index 0000000..32d1098
--- /dev/null
+++ b/Core.Cerberos.Adapters/UserExistenceAdapter.cs
@@ -0,0 +1,22 @@
+// ***********************************************************************
+//
+// Heath
+//
+// ***********************************************************************
+
+using System.Text.Json.Serialization;
+
+namespace Core.Cerberos.Adapters
+{
+ ///
+ /// Adapter representing a user.
+ ///
+ public class UserExistenceAdapter
+ {
+ ///
+ /// user existence.
+ ///
+ [JsonPropertyName("existence")]
+ public bool Existence { get; set; }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8917575
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+SharedLibs
\ No newline at end of file