Compare commits
7 Commits
8408b80c35
...
470031af39
Author | SHA1 | Date | |
---|---|---|---|
470031af39 | |||
916b3795ed | |||
f4ad1518ef | |||
ac7bbde75e | |||
47a57693f8 | |||
d05ba5349f | |||
5fde5bd396 |
@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDto", "ApiDto\ApiDto.csproj", "{0335FA36-E137-453F-853B-916674C168FE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Security", "Security\Security.csproj", "{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -51,6 +53,10 @@ Global
|
||||
{0335FA36-E137-453F-853B-916674C168FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0335FA36-E137-453F-853B-916674C168FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{47A3C065-4E1D-4B1E-AAB4-2BB8F40E56B4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -5,5 +5,6 @@ public class PreAuthToken
|
||||
public required string Fingerprint { get; set; }
|
||||
public required string UserAgent { get; set; }
|
||||
public required string UserId { get; set; }
|
||||
public required string Ip { get; set; }
|
||||
public required string Token { get; set; }
|
||||
}
|
28
Security/Services/GeneratorKey.cs
Normal file
28
Security/Services/GeneratorKey.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Mirea.Api.Security.Services;
|
||||
|
||||
public static class GeneratorKey
|
||||
{
|
||||
public static ReadOnlySpan<byte> GenerateBytes(int size)
|
||||
{
|
||||
var key = new byte[size];
|
||||
using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
|
||||
rng.GetNonZeroBytes(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
public static string GenerateBase64(int size) =>
|
||||
Convert.ToBase64String(GenerateBytes(size));
|
||||
|
||||
public static string GenerateString(int size)
|
||||
{
|
||||
var randomBytes = GenerateBytes(size);
|
||||
Span<byte> utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)];
|
||||
|
||||
Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _);
|
||||
return Encoding.UTF8.GetString(utf8Bytes);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using Konscious.Security.Cryptography;
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Mirea.Api.Security.Services;
|
||||
@ -41,29 +40,9 @@ public class PasswordHashService
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<byte> GenerateRandomKeyBytes(int size)
|
||||
{
|
||||
var key = new byte[size];
|
||||
using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
|
||||
rng.GetNonZeroBytes(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
public static string GenerateRandomKeyStringBase64(int size) =>
|
||||
Convert.ToBase64String(GenerateRandomKeyBytes(size));
|
||||
|
||||
public static string GenerateRandomKeyString(int size)
|
||||
{
|
||||
var randomBytes = GenerateRandomKeyBytes(size);
|
||||
Span<byte> utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)];
|
||||
|
||||
Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _);
|
||||
return Encoding.UTF8.GetString(utf8Bytes);
|
||||
}
|
||||
|
||||
public (string Salt, string Hash) HashPassword(string password)
|
||||
{
|
||||
var salt = GenerateRandomKeyBytes(SaltSize);
|
||||
var salt = GeneratorKey.GenerateBytes(SaltSize);
|
||||
var hash = HashPassword(password, salt);
|
||||
|
||||
return (Convert.ToBase64String(salt), Convert.ToBase64String(hash));
|
||||
|
@ -3,6 +3,7 @@ using Mirea.Api.Security.Common.Dto.Requests;
|
||||
using Mirea.Api.Security.Common.Dto.Responses;
|
||||
using Mirea.Api.Security.Common.Interfaces;
|
||||
using System;
|
||||
using System.Security;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -13,30 +14,51 @@ public class PreAuthService(ICacheService cache)
|
||||
{
|
||||
public TimeSpan Lifetime { private get; init; }
|
||||
|
||||
private static string GenerateFirstAuthToken() => Guid.NewGuid().ToString().Replace("-", "");
|
||||
private static string GeneratePreAuthToken() => Guid.NewGuid().ToString().Replace("-", "") +
|
||||
GeneratorKey.GenerateString(16);
|
||||
|
||||
public async Task<PreAuthTokenResponse> CreateLoginTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default)
|
||||
private static string GetPreAuthCacheKey(string fingerprint) => $"{fingerprint}_pre_auth_token";
|
||||
|
||||
public async Task<PreAuthTokenResponse> GeneratePreAuthTokenAsync(TokenRequest request, string userId, CancellationToken cancellation = default)
|
||||
{
|
||||
var firstAuthToken = GenerateFirstAuthToken();
|
||||
var preAuthToken = GeneratePreAuthToken();
|
||||
|
||||
var loginStructure = new PreAuthToken
|
||||
var preAuthTokenStruct = new PreAuthToken
|
||||
{
|
||||
Fingerprint = request.Fingerprint,
|
||||
UserId = userId,
|
||||
UserAgent = request.UserAgent,
|
||||
Token = firstAuthToken
|
||||
Token = preAuthToken,
|
||||
Ip = request.Ip
|
||||
};
|
||||
|
||||
await cache.SetAsync(
|
||||
request.Fingerprint,
|
||||
JsonSerializer.SerializeToUtf8Bytes(loginStructure),
|
||||
GetPreAuthCacheKey(request.Fingerprint),
|
||||
JsonSerializer.SerializeToUtf8Bytes(preAuthTokenStruct),
|
||||
Lifetime,
|
||||
cancellation);
|
||||
|
||||
return new PreAuthTokenResponse
|
||||
{
|
||||
Token = firstAuthToken,
|
||||
Token = preAuthToken,
|
||||
ExpiresIn = DateTime.UtcNow.Add(Lifetime)
|
||||
};
|
||||
}
|
||||
public async Task<string> MatchToken(TokenRequest request, string preAuthToken, CancellationToken cancellation = default)
|
||||
{
|
||||
var preAuthTokenStruct = await cache.GetAsync<PreAuthToken>(GetPreAuthCacheKey(request.Fingerprint), cancellation)
|
||||
?? throw new SecurityException($"The token was not found using fingerprint \"{request.Fingerprint}\"");
|
||||
|
||||
if (preAuthTokenStruct == null ||
|
||||
preAuthTokenStruct.Token != preAuthToken ||
|
||||
(preAuthTokenStruct.UserAgent != request.UserAgent &&
|
||||
preAuthTokenStruct.Ip != request.Ip))
|
||||
{
|
||||
throw new SecurityException("It was not possible to verify the authenticity of the token");
|
||||
}
|
||||
|
||||
await cache.RemoveAsync(GetPreAuthCacheKey(request.Fingerprint), cancellation);
|
||||
|
||||
return preAuthTokenStruct.UserId;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user