Skip to content

Commit 032dcba

Browse files
committed
2 parents e62d46c + 6329051 commit 032dcba

28 files changed

Lines changed: 691 additions & 11 deletions

Codout.Framework.Api/Codout.Framework.Api.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="AutoMapper" Version="16.1.0" />
9+
<PackageReference Include="AutoMapper" Version="16.1.1" />
1010
</ItemGroup>
1111

1212
<ItemGroup>

Codout.Framework.Application/Codout.Framework.Application.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="AutoMapper" Version="16.1.0" />
9+
<PackageReference Include="AutoMapper" Version="16.1.1" />
1010
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
1111
</ItemGroup>
1212

Codout.Framework.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Framework.Data", "Co
5858
EndProject
5959
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Mailer.Razor", "Codout.Mailer.Razor\Codout.Mailer.Razor.csproj", "{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}"
6060
EndProject
61+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Security", "Security", "{F65D869E-54A1-41D2-A6C3-EAD78678ADC4}"
62+
EndProject
63+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Core", "src\Security\Codout.Security.Core\Codout.Security.Core.csproj", "{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}"
64+
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Argon2", "src\Security\Codout.Security.Argon2\Codout.Security.Argon2.csproj", "{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}"
66+
EndProject
67+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Bcrypt", "src\Security\Codout.Security.Bcrypt\Codout.Security.Bcrypt.csproj", "{732C166B-24BF-4A84-8C5F-4EDB6340CE45}"
68+
EndProject
69+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codout.Security.Scrypt", "src\Security\Codout.Security.Scrypt\Codout.Security.Scrypt.csproj", "{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}"
70+
EndProject
6171
Global
6272
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6373
Debug|Any CPU = Debug|Any CPU
@@ -136,6 +146,22 @@ Global
136146
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
137147
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
138148
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7}.Release|Any CPU.Build.0 = Release|Any CPU
149+
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
150+
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Debug|Any CPU.Build.0 = Debug|Any CPU
151+
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|Any CPU.ActiveCfg = Release|Any CPU
152+
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D}.Release|Any CPU.Build.0 = Release|Any CPU
153+
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
154+
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
155+
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
156+
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA}.Release|Any CPU.Build.0 = Release|Any CPU
157+
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
158+
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Debug|Any CPU.Build.0 = Debug|Any CPU
159+
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|Any CPU.ActiveCfg = Release|Any CPU
160+
{732C166B-24BF-4A84-8C5F-4EDB6340CE45}.Release|Any CPU.Build.0 = Release|Any CPU
161+
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
162+
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
163+
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
164+
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6}.Release|Any CPU.Build.0 = Release|Any CPU
139165
EndGlobalSection
140166
GlobalSection(SolutionProperties) = preSolution
141167
HideSolutionNode = FALSE
@@ -161,6 +187,10 @@ Global
161187
{7A5F8194-FBD0-47C0-A8F8-1EB68662BE1E} = {B6832CA4-604B-4DC8-908B-06405133B6B6}
162188
{52DE6A78-4383-E3EC-3109-2410414F8EEE} = {B6832CA4-604B-4DC8-908B-06405133B6B6}
163189
{B42C234C-CEB5-76D7-752D-DE71B2F1E9F7} = {992EAEDC-B75F-45CA-8B2C-840E3F9B5133}
190+
{6764EB74-2D58-45E8-8F5B-942B4FB1C47D} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
191+
{3D7FBB69-1515-4A0E-BAED-D87C08C921EA} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
192+
{732C166B-24BF-4A84-8C5F-4EDB6340CE45} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
193+
{EF60A915-F3C8-4ABE-A6B3-5086F7093FB6} = {F65D869E-54A1-41D2-A6C3-EAD78678ADC4}
164194
EndGlobalSection
165195
GlobalSection(ExtensibilityGlobals) = postSolution
166196
SolutionGuid = {F077B358-8F0D-4791-8E85-DDED3550C27A}

Codout.Mailer.Razor/Configuration/ConfigureServices.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using Codout.Mailer.Interfaces;
33
using Microsoft.Extensions.DependencyInjection;
44
using Microsoft.Extensions.FileProviders;
@@ -8,8 +8,8 @@ namespace Codout.Mailer.Razor.Configuration;
88
public static class ConfigureServices
99
{
1010
/// <summary>
11-
/// Registra o template engine Razor nativo do ASP.NET Core para renderização de templates de e-mail.
12-
/// Deve ser chamado após <c>AddMailer()</c>.
11+
/// Registra o template engine Razor nativo do ASP.NET Core para renderização de templates de e-mail.
12+
/// Deve ser chamado após <c>AddMailer()</c>.
1313
/// </summary>
1414
public static IServiceCollection AddMailerRazor(
1515
this IServiceCollection services,
@@ -32,3 +32,5 @@ public static IServiceCollection AddMailerRazor(
3232
return services;
3333
}
3434
}
35+
36+

Codout.Mailer.Razor/Configuration/RazorMailerOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
using System;
1+
using System;
22
using System.Reflection;
33

44
namespace Codout.Mailer.Razor.Configuration;
55

66
/// <summary>
7-
/// Opções de configuração para o template engine Razor do ASP.NET Core
7+
/// Opções de configuração para o template engine Razor do ASP.NET Core
88
/// </summary>
99
public class RazorMailerOptions
1010
{
1111
/// <summary>
12-
/// Assembly que contém os templates Razor embarcados como recursos
12+
/// Assembly que contém os templates Razor embarcados como recursos
1313
/// </summary>
1414
public Assembly TemplateAssembly { get; set; }
1515

@@ -19,7 +19,7 @@ public class RazorMailerOptions
1919
public string RootNamespace { get; set; }
2020

2121
/// <summary>
22-
/// Habilita o cache de templates compilados em memória
22+
/// Habilita o cache de templates compilados em memória
2323
/// </summary>
2424
public bool EnableCache { get; set; } = true;
2525

Codout.Mailer.Razor/RazorViewTemplateEngine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.IO;
33
using System.Threading.Tasks;
44
using Codout.Mailer.Interfaces;
@@ -33,7 +33,7 @@ public async Task<string> RenderAsync<T>(string templateKey, T model)
3333
{
3434
var searchedLocations = string.Join(", ", viewResult.SearchedLocations ?? []);
3535
throw new InvalidOperationException(
36-
$"Template '{templateKey}' não encontrado. Locais pesquisados: {searchedLocations}");
36+
$"Template '{templateKey}' não encontrado. Locais pesquisados: {searchedLocations}");
3737
}
3838

3939
await using var writer = new StringWriter();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Codout.Security.Core;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace Codout.Security.Argon2;
5+
6+
public static class Argon2Extensions
7+
{
8+
public static IServiceCollection UseArgon2(this IPasswordHashBuilder builder, Action<Argon2Options>? configure = null)
9+
{
10+
builder.Services.Configure<ImprovedPasswordHasherOptions>(options =>
11+
{
12+
options.Strength = builder.Options.Strength;
13+
});
14+
15+
if (configure != null)
16+
builder.Services.Configure(configure);
17+
18+
return builder.Services.AddScoped<IPasswordHasher, ArgonPasswordHash>();
19+
}
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Codout.Security.Argon2;
2+
3+
public class Argon2Options
4+
{
5+
/// <summary>
6+
/// opslimit represents a maximum amount of computations to perform.
7+
/// Raising this number will make the function require more CPU cycles to compute a key.
8+
/// When set along with MemLimit, overrides the Strength setting.
9+
/// </summary>
10+
public long? OpsLimit { get; set; }
11+
12+
/// <summary>
13+
/// memlimit is the maximum amount of RAM that the function will use, in bytes.
14+
/// When set along with OpsLimit, overrides the Strength setting.
15+
/// </summary>
16+
public int? MemLimit { get; set; }
17+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using Codout.Security.Core;
2+
using Microsoft.Extensions.Options;
3+
using Sodium;
4+
5+
namespace Codout.Security.Argon2
6+
{
7+
public class ArgonPasswordHash(
8+
IOptions<ImprovedPasswordHasherOptions>? optionsAccessor = null,
9+
IOptions<Argon2Options>? argon2OptionsAccessor = null) : IPasswordHasher
10+
{
11+
private readonly ImprovedPasswordHasherOptions _options = optionsAccessor?.Value ?? new ImprovedPasswordHasherOptions();
12+
private readonly Argon2Options _argon2Options = argon2OptionsAccessor?.Value ?? new Argon2Options();
13+
14+
public string HashPassword(string password)
15+
{
16+
if (string.IsNullOrEmpty(password))
17+
throw new ArgumentNullException(nameof(password), $"{nameof(password)} should not be null");
18+
19+
if (_argon2Options.OpsLimit.HasValue && _argon2Options.MemLimit.HasValue)
20+
return PasswordHash.ArgonHashString(password, _argon2Options.OpsLimit.Value, _argon2Options.MemLimit.Value).TrimEnd('\0');
21+
22+
return _options.Strength switch
23+
{
24+
PasswordHasherStrength.Interactive => PasswordHash.ArgonHashString(password).TrimEnd('\0'),
25+
PasswordHasherStrength.Moderate => PasswordHash.ArgonHashString(password, PasswordHash.StrengthArgon.Moderate).TrimEnd('\0'),
26+
PasswordHasherStrength.Sensitive => PasswordHash.ArgonHashString(password, PasswordHash.StrengthArgon.Sensitive).TrimEnd('\0'),
27+
_ => throw new ArgumentOutOfRangeException()
28+
};
29+
}
30+
31+
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
32+
{
33+
if (string.IsNullOrEmpty(hashedPassword))
34+
throw new ArgumentNullException(nameof(hashedPassword), $"{nameof(hashedPassword)} should not be null");
35+
36+
if (string.IsNullOrEmpty(providedPassword))
37+
throw new ArgumentNullException(nameof(providedPassword), $"{nameof(providedPassword)} should not be null");
38+
39+
if (!PasswordHash.ArgonHashStringVerify(hashedPassword, providedPassword))
40+
return PasswordVerificationResult.Failed;
41+
42+
return NeedsRehash(hashedPassword)
43+
? PasswordVerificationResult.SuccessRehashNeeded
44+
: PasswordVerificationResult.Success;
45+
}
46+
47+
private bool NeedsRehash(string hashedPassword)
48+
{
49+
// Parse Argon2 hash format: $argon2id$v=19$m=65536,t=3,p=1$salt$hash
50+
var parts = hashedPassword.Split('$');
51+
if (parts.Length < 4)
52+
return false;
53+
54+
var paramPart = parts[3];
55+
var parameters = paramPart.Split(',');
56+
57+
long storedMemKib = 0;
58+
long storedTimeCost = 0;
59+
60+
foreach (var param in parameters)
61+
{
62+
if (param.StartsWith("m=") && long.TryParse(param.AsSpan(2), out var m))
63+
storedMemKib = m;
64+
else if (param.StartsWith("t=") && long.TryParse(param.AsSpan(2), out var t))
65+
storedTimeCost = t;
66+
}
67+
68+
if (storedMemKib == 0 || storedTimeCost == 0)
69+
return false;
70+
71+
var (expectedTimeCost, expectedMemKib) = GetExpectedParameters();
72+
return storedTimeCost < expectedTimeCost || storedMemKib < expectedMemKib;
73+
}
74+
75+
private (long timeCost, long memKib) GetExpectedParameters()
76+
{
77+
if (_argon2Options.OpsLimit.HasValue && _argon2Options.MemLimit.HasValue)
78+
return (_argon2Options.OpsLimit.Value, _argon2Options.MemLimit.Value / 1024);
79+
80+
return _options.Strength switch
81+
{
82+
PasswordHasherStrength.Interactive => (2, 65536), // 64 MiB
83+
PasswordHasherStrength.Moderate => (3, 262144), // 256 MiB
84+
PasswordHasherStrength.Sensitive => (4, 1048576), // 1 GiB
85+
_ => (4, 1048576)
86+
};
87+
}
88+
}
89+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
11+
<PackageReference Include="Sodium.Core" Version="1.4.0" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Codout.Security.Core\Codout.Security.Core.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)