diff --git a/.gitignore b/.gitignore
index 9491a2f..44a94b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+/PortBlog.API/appsettings.Production.json
diff --git a/AesEncryption/AesEncryption.csproj b/AesEncryption/AesEncryption.csproj
new file mode 100644
index 0000000..2150e37
--- /dev/null
+++ b/AesEncryption/AesEncryption.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/AesEncryption/Program.cs b/AesEncryption/Program.cs
new file mode 100644
index 0000000..c849d27
--- /dev/null
+++ b/AesEncryption/Program.cs
@@ -0,0 +1,138 @@
+// See https://aka.ms/new-console-template for more information
+using System.Security.Cryptography;
+
+string keyBase64;
+string cipherText;
+string vectorBase64;
+string plainText;
+
+Console.WriteLine("Welcome to the Aes Encryption/Decryption tool");
+Select:
+Console.WriteLine("Please select the action you want to perform: /n Press 1 to Generate Key. /n Press 2 to Encrypt using the key. /n Press 3 to Decrpt using the key.");
+
+string? action = Console.ReadLine();
+
+if (action == null || !int.TryParse(action, out int actionId) || !new List{ 1, 2, 3 }.Contains(actionId))
+{
+ Console.WriteLine("Invalid option. /n");
+ goto Select;
+}
+
+switch (actionId)
+{
+ case 1:
+ Console.WriteLine("Creating Aes Encryption 256 bit key");
+ using (Aes aesAlgorithm = Aes.Create())
+ {
+ aesAlgorithm.KeySize = 256;
+ aesAlgorithm.GenerateKey();
+ keyBase64 = Convert.ToBase64String(aesAlgorithm.Key);
+ Console.WriteLine($"Aes Key Size : {aesAlgorithm.KeySize}");
+ Console.WriteLine("Here is the Aes key in Base64:");
+ Console.WriteLine(keyBase64);
+ }
+ break;
+ case 2:
+ Console.WriteLine("Encrypt Text");
+ Console.WriteLine("Provide the Aes Key in base64 format :");
+ keyBase64 = Console.ReadLine();
+ Console.WriteLine("--------------------------------------------------------------");
+ Console.WriteLine("Please enter the text that you want to encrypt:");
+ plainText = Console.ReadLine();
+ Console.WriteLine("--------------------------------------------------------------");
+ cipherText = EncryptDataWithAes(plainText, keyBase64, out vectorBase64);
+
+ Console.WriteLine("--------------------------------------------------------------");
+ Console.WriteLine("Here is the cipher text with vector:");
+ Console.WriteLine(cipherText + ":" + vectorBase64);
+ break;
+ case 3:
+ Console.WriteLine("Please enter the text that you want to decrypt:");
+ cipherText = Console.ReadLine();
+ Console.WriteLine("--------------------------------------------------------------");
+
+ Console.WriteLine("Provide the Aes Key:");
+ keyBase64 = Console.ReadLine();
+ Console.WriteLine("--------------------------------------------------------------");
+
+ vectorBase64 = cipherText.Split(":")[1];
+ cipherText = cipherText.Split(":")[0];
+
+ plainText = DecryptDataWithAes(cipherText, keyBase64, vectorBase64);
+
+ Console.WriteLine("--------------------------------------------------------------");
+ Console.WriteLine("Here is the decrypted data:");
+ Console.WriteLine(plainText);
+ break;
+}
+
+goto Select;
+
+
+static string EncryptDataWithAes(string plainText, string keyBase64, out string vectorBase64)
+{
+ using (Aes aesAlgorithm = Aes.Create())
+ {
+ aesAlgorithm.Key = Convert.FromBase64String(keyBase64);
+ aesAlgorithm.GenerateIV();
+ Console.WriteLine($"Aes Cipher Mode : {aesAlgorithm.Mode}");
+ Console.WriteLine($"Aes Padding Mode: {aesAlgorithm.Padding}");
+ Console.WriteLine($"Aes Key Size : {aesAlgorithm.KeySize}");
+
+ //set the parameters with out keyword
+ vectorBase64 = Convert.ToBase64String(aesAlgorithm.IV);
+
+ // Create encryptor object
+ ICryptoTransform encryptor = aesAlgorithm.CreateEncryptor();
+
+ byte[] encryptedData;
+
+ //Encryption will be done in a memory stream through a CryptoStream object
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
+ {
+ using (StreamWriter sw = new StreamWriter(cs))
+ {
+ sw.Write(plainText);
+ }
+ encryptedData = ms.ToArray();
+ }
+ }
+
+ return Convert.ToBase64String(encryptedData);
+ }
+}
+
+static string DecryptDataWithAes(string cipherText, string keyBase64, string vectorBase64)
+{
+ using (Aes aesAlgorithm = Aes.Create())
+ {
+ aesAlgorithm.Key = Convert.FromBase64String(keyBase64);
+ aesAlgorithm.IV = Convert.FromBase64String(vectorBase64);
+
+ Console.WriteLine($"Aes Cipher Mode : {aesAlgorithm.Mode}");
+ Console.WriteLine($"Aes Padding Mode: {aesAlgorithm.Padding}");
+ Console.WriteLine($"Aes Key Size : {aesAlgorithm.KeySize}");
+ Console.WriteLine($"Aes Block Size : {aesAlgorithm.BlockSize}");
+
+
+ // Create decryptor object
+ ICryptoTransform decryptor = aesAlgorithm.CreateDecryptor();
+
+ byte[] cipher = Convert.FromBase64String(cipherText);
+
+ //Decryption will be done in a memory stream through a CryptoStream object
+ using (MemoryStream ms = new MemoryStream(cipher))
+ {
+ using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
+ {
+ using (StreamReader sr = new StreamReader(cs))
+ {
+ return sr.ReadToEnd();
+ }
+ }
+ }
+ }
+}
+
diff --git a/PortBlog.API.sln b/PortBlog.API.sln
index 3246ead..22ea6e5 100644
--- a/PortBlog.API.sln
+++ b/PortBlog.API.sln
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34701.34
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortBlog.API", "PortBlog.API\PortBlog.API.csproj", "{2E50B5D7-56E2-4E89-8742-BB57FF4245F9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PortBlog.API", "PortBlog.API\PortBlog.API.csproj", "{2E50B5D7-56E2-4E89-8742-BB57FF4245F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AesEncryption", "AesEncryption\AesEncryption.csproj", "{26654BFD-EE9B-49BA-84BA-9156AC348076}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
{2E50B5D7-56E2-4E89-8742-BB57FF4245F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E50B5D7-56E2-4E89-8742-BB57FF4245F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E50B5D7-56E2-4E89-8742-BB57FF4245F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26654BFD-EE9B-49BA-84BA-9156AC348076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26654BFD-EE9B-49BA-84BA-9156AC348076}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26654BFD-EE9B-49BA-84BA-9156AC348076}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26654BFD-EE9B-49BA-84BA-9156AC348076}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/PortBlog.API/Extensions/ConfigurationExtensions.cs b/PortBlog.API/Extensions/ConfigurationExtensions.cs
new file mode 100644
index 0000000..dd0763a
--- /dev/null
+++ b/PortBlog.API/Extensions/ConfigurationExtensions.cs
@@ -0,0 +1,44 @@
+using System.Security.Cryptography;
+
+namespace PortBlog.API.Extensions
+{
+ public static class ConfigurationExtensions
+ {
+ public static string DecryptConnectionString(this IConfiguration configuration, string encryptedConnectionString)
+ {
+ string keyBase64 = configuration.GetValue("ConnectionStrings:Key").ToString();
+
+ string vectorBase64 = encryptedConnectionString.Split(":")[1];
+ string cipherText = encryptedConnectionString.Split(":")[0];
+
+ using (Aes aesAlgorithm = Aes.Create())
+ {
+ aesAlgorithm.Key = Convert.FromBase64String(keyBase64);
+ aesAlgorithm.IV = Convert.FromBase64String(vectorBase64);
+
+ Console.WriteLine($"Aes Cipher Mode : {aesAlgorithm.Mode}");
+ Console.WriteLine($"Aes Padding Mode: {aesAlgorithm.Padding}");
+ Console.WriteLine($"Aes Key Size : {aesAlgorithm.KeySize}");
+ Console.WriteLine($"Aes Block Size : {aesAlgorithm.BlockSize}");
+
+
+ // Create decryptor object
+ ICryptoTransform decryptor = aesAlgorithm.CreateDecryptor();
+
+ byte[] cipher = Convert.FromBase64String(cipherText);
+
+ //Decryption will be done in a memory stream through a CryptoStream object
+ using (MemoryStream ms = new MemoryStream(cipher))
+ {
+ using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
+ {
+ using (StreamReader sr = new StreamReader(cs))
+ {
+ return sr.ReadToEnd();
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/PortBlog.API/Middleware/ApiKeyMiddleware.cs b/PortBlog.API/Middleware/ApiKeyMiddleware.cs
new file mode 100644
index 0000000..838b4ee
--- /dev/null
+++ b/PortBlog.API/Middleware/ApiKeyMiddleware.cs
@@ -0,0 +1,40 @@
+using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+using System.Net;
+
+namespace PortBlog.API.Middleware
+{
+ public class ApiKeyMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private const string APIKEY = "XApiKey";
+ public ApiKeyMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ if (!context.Request.Headers.TryGetValue(APIKEY, out var extractedApiKey))
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync("Api key was not provided");
+ return;
+ }
+
+ var appSettings = context.RequestServices.GetRequiredService();
+
+ var apiKey = appSettings.GetValue(APIKEY);
+
+ if (apiKey != null && !apiKey.Equals(extractedApiKey))
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync("Unauthorized client");
+ return;
+ }
+
+ await _next(context);
+ }
+ }
+}
diff --git a/PortBlog.API/Models/PostDto.cs b/PortBlog.API/Models/PostDto.cs
index f07e809..0ac0944 100644
--- a/PortBlog.API/Models/PostDto.cs
+++ b/PortBlog.API/Models/PostDto.cs
@@ -19,5 +19,11 @@
public int Views { get; set; } = 0;
public int Comments { get; set; } = 0;
+
+ public string? Image { get; set; }
+
+ public string? CreatedDate { get; set; }
+
+ public string? ModifiedDate { get; set; }
}
}
diff --git a/PortBlog.API/Models/ResumeDto.cs b/PortBlog.API/Models/ResumeDto.cs
index cc5e82d..d124ed7 100644
--- a/PortBlog.API/Models/ResumeDto.cs
+++ b/PortBlog.API/Models/ResumeDto.cs
@@ -31,5 +31,13 @@
public ICollection Hobbies { get; set; } = new List();
public ICollection Projects { get; set; } = new List();
+
+ public ICollection ProjectsCategories
+ {
+ get
+ {
+ return Projects.Select(p => p.Category).Distinct().ToList();
+ }
+ }
}
}
diff --git a/PortBlog.API/Profiles/BlogProfile.cs b/PortBlog.API/Profiles/BlogProfile.cs
index 23e9afe..b1c8575 100644
--- a/PortBlog.API/Profiles/BlogProfile.cs
+++ b/PortBlog.API/Profiles/BlogProfile.cs
@@ -9,7 +9,15 @@ namespace PortBlog.API.Profiles
public BlogProfile()
{
CreateMap();
- CreateMap();
+ CreateMap()
+ .ForMember(
+ dest => dest.CreatedDate,
+ opts => opts.MapFrom(src => src.CreatedDate != null ? src.CreatedDate.Value.ToString("MMM dd, yyyy") : string.Empty)
+ )
+ .ForMember(
+ dest => dest.ModifiedDate,
+ opts => opts.MapFrom(src => src.CreatedDate != null ? src.ModifiedDate.Value.ToString("MMM dd, yyyy") : string.Empty)
+ );
}
}
}
diff --git a/PortBlog.API/Profiles/ResumeProfile.cs b/PortBlog.API/Profiles/ResumeProfile.cs
index 06d7ef4..d418302 100644
--- a/PortBlog.API/Profiles/ResumeProfile.cs
+++ b/PortBlog.API/Profiles/ResumeProfile.cs
@@ -24,7 +24,7 @@ namespace PortBlog.API.Profiles
CreateMap()
.ForMember(
dest => dest.Period,
- src => src.MapFrom(src => src.StartDate.Year + " - " + (src.EndDate != null ? src.EndDate.Value.Year : "Present"))
+ src => src.MapFrom(src => src.StartDate.ToString("MMM yyyy") + " - " + (src.EndDate != null ? src.EndDate.Value.ToString("MMM yyyy") : "Present"))
)
.ForMember
(
diff --git a/PortBlog.API/Program.cs b/PortBlog.API/Program.cs
index 8471706..1b4b01c 100644
--- a/PortBlog.API/Program.cs
+++ b/PortBlog.API/Program.cs
@@ -1,7 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
+using Microsoft.OpenApi.Models;
using PortBlog.API.DbContexts;
using PortBlog.API.Extensions;
+using PortBlog.API.Middleware;
using Serilog;
Log.Logger = new LoggerConfiguration()
@@ -25,10 +27,42 @@ builder.Services.AddProblemDetails();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+builder.Services.AddSwaggerGen(c =>
+{
+ c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
+ {
+ Description = "ApiKey must appear in header",
+ Type = SecuritySchemeType.ApiKey,
+ Name = "XApiKey",
+ In = ParameterLocation.Header,
+ Scheme = "ApiKeyScheme"
+ });
+
+ var key = new OpenApiSecurityScheme()
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "ApiKey"
+ },
+ In = ParameterLocation.Header
+ };
+
+ var requirement = new OpenApiSecurityRequirement()
+ {
+ {key, new List {} }
+ };
+
+ c.AddSecurityRequirement(requirement);
+});
var connectionString = builder.Configuration.GetConnectionString("PortBlogDBConnectionString");
+if (builder.Configuration.GetValue("ConnectionStrings:Encryption"))
+{
+ connectionString = builder.Configuration.DecryptConnectionString(connectionString);
+}
+
if (string.IsNullOrEmpty(connectionString))
{
throw new Exception("Connection string cannot be empty");
@@ -66,6 +100,8 @@ app.UseStaticFiles(new StaticFileOptions()
app.UseAuthorization();
+app.UseMiddleware();
+
app.MapControllers();
app.Run();
diff --git a/PortBlog.API/Repositories/ResumeRepository.cs b/PortBlog.API/Repositories/ResumeRepository.cs
index 4162797..916646b 100644
--- a/PortBlog.API/Repositories/ResumeRepository.cs
+++ b/PortBlog.API/Repositories/ResumeRepository.cs
@@ -24,7 +24,7 @@ namespace PortBlog.API.Repositories
.ThenInclude(e => e.Details)
.Include(r => r.SocialLinks)
.ThenInclude(s => s.Blog)
- .ThenInclude(b => b.Posts.Take(5))
+ .ThenInclude(b => b.Posts.OrderByDescending(p => p.Likes).Take(5))
.Include(r => r.Hobbies)
.Include(r => r.Certifications)
.Include(r => r.Skills)
diff --git a/PortBlog.API/appsettings.Development.json b/PortBlog.API/appsettings.Development.json
index 84b1da8..6c5f3a2 100644
--- a/PortBlog.API/appsettings.Development.json
+++ b/PortBlog.API/appsettings.Development.json
@@ -1,11 +1,14 @@
{
"ConnectionStrings": {
- "PortBlogDBConnectionString": "SERVER=192.168.0.197; DATABASE=cv_blog; UID=PortBlogDevUser; PWD=p@$$w0rd1234"
+ "PortBlogDBConnectionString": "SERVER=192.168.0.197; DATABASE=cv_blog; UID=PortBlogDevUser; PWD=p@$$w0rd1234",
+ "Encryption": "false",
+ "Key": "rgdBsYjrgQV9YaE+6QFK5oyTOWwbl2bSWkuc2JXcIyw="
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
- }
+ },
+ "XApiKey": "c6eAXYcNT873TT7BfMgQyS4ii7hxa53TLEUN7pAGaaU="
}
diff --git a/PortBlog.API/appsettings.Production.json b/PortBlog.API/appsettings.Production.json
deleted file mode 100644
index a683bd2..0000000
--- a/PortBlog.API/appsettings.Production.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "ConnectionStrings": {
- "PortBlogDBConnectionString": "SERVER=10.1.0.10;DATABASE=cv_blog;UID=PortBlogProdUser;PWD=pr0dp@$$w0rd6534"
- },
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
\ No newline at end of file