- fix: auth renew and null fix
This commit is contained in:
@@ -2,16 +2,17 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Nebula.Shared.Configurations.Migrations;
|
||||
using Nebula.Shared.Models;
|
||||
using Nebula.Shared.Services;
|
||||
using Nebula.Shared.Utils;
|
||||
|
||||
namespace Nebula.Shared.ConfigMigrations;
|
||||
|
||||
public class ProfileMigrationV2(string oldName, string newName)
|
||||
: BaseConfigurationMigration<ProfileAuthCredentialsV2[], AuthTokenCredentials[]>(oldName, newName)
|
||||
: BaseConfigurationMigration<ProfileAuthCredentials[], string[]>(oldName, newName)
|
||||
{
|
||||
protected override async Task<AuthTokenCredentials[]> Migrate(IServiceProvider serviceProvider, ProfileAuthCredentialsV2[] oldValue, ILoadingHandler loadingHandler)
|
||||
protected override async Task<string[]> Migrate(IServiceProvider serviceProvider, ProfileAuthCredentials[] oldValue, ILoadingHandler loadingHandler)
|
||||
{
|
||||
loadingHandler.SetLoadingMessage("Migrating Profile V2 -> V3");
|
||||
var list = new List<AuthTokenCredentials>();
|
||||
loadingHandler.SetLoadingMessage("Migrating Profile V2 -> V4");
|
||||
var list = new List<string>();
|
||||
var authService = serviceProvider.GetRequiredService<AuthService>();
|
||||
var logger = serviceProvider.GetRequiredService<DebugService>().GetLogger("ProfileMigrationV2");
|
||||
foreach (var oldCredentials in oldValue)
|
||||
@@ -19,8 +20,8 @@ public class ProfileMigrationV2(string oldName, string newName)
|
||||
try
|
||||
{
|
||||
loadingHandler.SetLoadingMessage($"Migrating {oldCredentials.Login}");
|
||||
var newCred = await authService.Auth(oldCredentials.Login, oldCredentials.Password, oldCredentials.AuthServer);
|
||||
list.Add(newCred);
|
||||
await authService.Auth(oldCredentials.Login, oldCredentials.Password, oldCredentials.AuthServer);
|
||||
list.Add(CryptographicStore.Encrypt(oldCredentials, CryptographicStore.GetComputerKey()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -34,7 +35,13 @@ public class ProfileMigrationV2(string oldName, string newName)
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ProfileAuthCredentialsV2(
|
||||
string Login,
|
||||
string Password,
|
||||
string AuthServer);
|
||||
public class ProfileMigrationV3V4(string oldName, string newName)
|
||||
: BaseConfigurationMigration<AuthTokenCredentials[], string[]>(oldName, newName)
|
||||
{
|
||||
protected override Task<string[]> Migrate(IServiceProvider serviceProvider, AuthTokenCredentials[] oldValue, ILoadingHandler loadingHandler)
|
||||
{
|
||||
Console.WriteLine("Removing profile v3 because no password is provided");
|
||||
return Task.FromResult(Array.Empty<string>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public class AuthService(
|
||||
var err = await e.Content.AsJson<AuthDenyError>();
|
||||
|
||||
if (err is null) throw;
|
||||
e.Dispose();
|
||||
throw new AuthException(err);
|
||||
}
|
||||
}
|
||||
@@ -60,14 +61,36 @@ public class AuthService(
|
||||
public async Task<AuthTokenCredentials> Refresh(AuthTokenCredentials tokenCredentials)
|
||||
{
|
||||
var authUrl = new Uri($"{tokenCredentials.AuthServer}api/auth/refresh");
|
||||
var newToken = await restService.PostAsync<LoginToken, TokenRequest>(
|
||||
TokenRequest.From(tokenCredentials), authUrl, cancellationService.Token);
|
||||
|
||||
return tokenCredentials with { Token = newToken };
|
||||
try
|
||||
{
|
||||
var newToken = await restService.PostAsync<LoginToken, TokenRequest>(
|
||||
TokenRequest.From(tokenCredentials), authUrl, cancellationService.Token);
|
||||
|
||||
return tokenCredentials with { Token = newToken };
|
||||
}
|
||||
catch (RestRequestException e)
|
||||
{
|
||||
if (e.StatusCode == HttpStatusCode.Unauthorized)
|
||||
throw new AuthTokenExpiredException(tokenCredentials);
|
||||
|
||||
e.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AuthTokenExpiredException : Exception
|
||||
{
|
||||
public AuthTokenExpiredException(AuthTokenCredentials credentials): base("Taken token is expired. Login: " + credentials.Login)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record AuthTokenCredentials(Guid UserId, LoginToken Token, string Login, string AuthServer);
|
||||
public sealed record ProfileAuthCredentials(
|
||||
string Login,
|
||||
string Password,
|
||||
string AuthServer);
|
||||
|
||||
public sealed record AuthDenyError(string[] Errors, AuthenticateDenyCode Code);
|
||||
|
||||
@@ -108,5 +131,4 @@ public sealed record TokenRequest(string Token)
|
||||
}
|
||||
|
||||
public static TokenRequest Empty { get; } = new TokenRequest("");
|
||||
|
||||
}
|
||||
@@ -29,7 +29,12 @@ public class RestService
|
||||
[Pure]
|
||||
public async Task<T> GetAsync<T>(Uri uri, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
var response = await _client.GetAsync(uri, cancellationToken);
|
||||
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri)
|
||||
{
|
||||
Version = HttpVersion.Version10,
|
||||
};
|
||||
|
||||
var response = await _client.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
|
||||
return await ReadResult<T>(response, cancellationToken, uri);
|
||||
}
|
||||
|
||||
@@ -85,10 +90,15 @@ public class RestService
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.AsJson<T>();
|
||||
var data = await response.Content.AsJson<T>();
|
||||
response.Dispose();
|
||||
return data;
|
||||
}
|
||||
|
||||
var ex = new RestRequestException(response.Content, response.StatusCode,
|
||||
$"Error while processing {uri.ToString()}: {response.ReasonPhrase}");
|
||||
|
||||
throw new RestRequestException(response.Content, response.StatusCode, $"Error while processing {uri.ToString()}: {response.ReasonPhrase}");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +106,13 @@ public sealed class NullResponse
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class RestRequestException(HttpContent content, HttpStatusCode statusCode, string message) : Exception(message)
|
||||
public sealed class RestRequestException(HttpContent content, HttpStatusCode statusCode, string message) : Exception(message), IDisposable
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; } = statusCode;
|
||||
public HttpContent Content { get; } = content;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Content.Dispose();
|
||||
}
|
||||
}
|
||||
82
Nebula.Shared/Utils/CryptographicStore.cs
Normal file
82
Nebula.Shared/Utils/CryptographicStore.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Nebula.Shared.Utils;
|
||||
|
||||
public static class CryptographicStore
|
||||
{
|
||||
public static string Encrypt(object value, byte[] key)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
|
||||
var iv = aes.IV;
|
||||
memoryStream.Write(iv, 0, iv.Length);
|
||||
|
||||
var serializedData = JsonSerializer.Serialize(value);
|
||||
|
||||
using CryptoStream cryptoStream = new(
|
||||
memoryStream,
|
||||
aes.CreateEncryptor(),
|
||||
CryptoStreamMode.Write);
|
||||
|
||||
using(StreamWriter encryptWriter = new(cryptoStream))
|
||||
{
|
||||
encryptWriter.WriteLine(serializedData);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
public static async Task<T> Decrypt<T>(string base64EncryptedValue, byte[] key)
|
||||
{
|
||||
using var memoryStream = new MemoryStream(Convert.FromBase64String(base64EncryptedValue));
|
||||
using var aes = Aes.Create();
|
||||
|
||||
var iv = new byte[aes.IV.Length];
|
||||
var numBytesToRead = aes.IV.Length;
|
||||
var numBytesRead = 0;
|
||||
while (numBytesToRead > 0)
|
||||
{
|
||||
var n = memoryStream.Read(iv, numBytesRead, numBytesToRead);
|
||||
if (n == 0) break;
|
||||
|
||||
numBytesRead += n;
|
||||
numBytesToRead -= n;
|
||||
}
|
||||
|
||||
|
||||
await using CryptoStream cryptoStream = new(
|
||||
memoryStream,
|
||||
aes.CreateDecryptor(key, iv),
|
||||
CryptoStreamMode.Read);
|
||||
|
||||
using StreamReader decryptReader = new(cryptoStream);
|
||||
var decryptedMessage = await decryptReader.ReadToEndAsync();
|
||||
return JsonSerializer.Deserialize<T>(decryptedMessage) ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public static byte[] GetKey(string input, int keySize = 256)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
throw new ArgumentException("Input string cannot be null or empty.", nameof(input));
|
||||
|
||||
var salt = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
using (var deriveBytes = new Rfc2898DeriveBytes(input, salt, 100_000, HashAlgorithmName.SHA256))
|
||||
{
|
||||
return deriveBytes.GetBytes(keySize / 8);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GetComputerKey(int keySize = 256)
|
||||
{
|
||||
var name = Environment.UserName;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = "LinuxUser";
|
||||
return GetKey(name, keySize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user