some api work

This commit is contained in:
Lapis
2026-06-05 09:21:20 -04:00
parent 9c37dda12d
commit 3b82968c8e
9 changed files with 450 additions and 215 deletions
+1
View File
@@ -6,3 +6,4 @@ riderModule.iml
/.idea
/CannedNet.API/Migrations
CannedNet.sln.DotSettings.user
CannedNet/Data/PrivateKey.pem
+1 -2
View File
@@ -22,6 +22,5 @@
"Discovery": "https://api.lapis.codes",
"PlayerSettings": "https://api.lapis.codes",
"Rendering": "https://api.lapis.codes",
"Launcher": "https://launcher.lapis.codes",
"Launcher": "https://launcher.lapis.codes"
}
+7 -164
View File
@@ -29,6 +29,13 @@ public class APIController
UseRudderStack = false
}));
app.MapGet("/api/config/v1/azurespeech", () => Results.Ok(new
{
Key = "dce8de5b297747d9b5bddcc7f19e8c5b",
Region = "eastus",
Enabled = false
}));
app.MapPost("/api/gamesight/event", async (HttpRequest request) =>
{
string? eventType = null;
@@ -418,31 +425,6 @@ public class APIController
return Results.Ok(avatar);
});
app.MapGet("/api/avatar/v3/saved", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
request.EnableBuffering();
var items = await db.SavedOutfits
.Where(i => i.OwnerAccountId == id)
.ToListAsync();
return Results.Json(items);
});
app.MapGet("/api/PlayerReporting/v1/moderationBlockDetails", () =>
Results.Content("{\"ReportCategory\":0,\"Duration\":0,\"GameSessionId\":0,\"IsHostKick\":false,\"Message\":\"\",\"PlayerIdReporter\":null,\"IsBan\":false}", "application/json"));
@@ -577,37 +559,6 @@ public class APIController
var json = File.ReadAllText("JSON/tempmyprogress.json");
return Results.Content(json, "application/json");
});
app.MapGet("/api/avatar/v2/gifts", async (HttpRequest request, AppDbContext db, JwtTokenService jwtService) =>
{
try
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
// Get all pending (unconsumed) gifts for this player
var pendingGifts = await db.ReceivedGifts
.Where(rg => rg.ReceiverAccountId == id && !rg.IsConsumed)
.ToListAsync();
return Results.Json(pendingGifts);
}
catch (Exception ex)
{
return Results.Problem($"Error retrieving gifts: {ex.Message}");
}
});
app.MapGet("/api/gamerewards/v1/pending", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
return "[]";
});
app.MapGet("/api/communityboard/v2/current", async (HttpRequest request, AppDbContext db) =>
{
var json = File.ReadAllText("JSON/communityboard.json");
@@ -618,10 +569,6 @@ public class APIController
// TODO ADD FUNCTIONALITY
return Results.Content("{\"Created\":[],\"Responses\":[]}", "application/json");
});
app.MapPost("/api/CampusCard/v1/UpdateAndGetSubscription", async (HttpRequest request, AppDbContext db) =>
{
return Results.Json(new { subscription = (object?)null, platformAccountSubscribedPlayerId = (object?)null });
});
app.MapGet("/api/storefronts/v1/p2p/betaEnabled", async (HttpRequest request, AppDbContext db) =>
{
return "false";
@@ -631,11 +578,6 @@ public class APIController
var json = File.ReadAllText("JSON/announcements.json");
return Results.Content(json, "application/json");
});
app.MapGet("/api/roomkeys/v1/mine", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
return "[]";
});
app.MapGet("/api/quickPlay/v1/getandclear", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
@@ -666,98 +608,6 @@ public class APIController
return Results.Json(bio);
});
app.MapPost("/api/accounts/v1/forplatformids", async (HttpRequest request, AppDbContext db) =>
{
request.EnableBuffering();
request.Body.Position = 0;
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var ids = new List<string>();
if (!string.IsNullOrWhiteSpace(body))
{
foreach (var pair in body.Split('&'))
{
var keyValue = pair.Split('=');
if (keyValue.Length == 2 && keyValue[0] == "Ids")
{
var idString = Uri.UnescapeDataString(keyValue[1]);
ids = idString.Split(',').ToList();
break;
}
}
}
var results = new List<object>();
foreach (var platformId in ids)
{
var cachedLogin = await db.CachedLogins.FirstOrDefaultAsync(c => c.PlatformID == platformId);
if (cachedLogin != null)
{
results.Add(new { accountId = cachedLogin.AccountId, platformId = platformId });
}
}
return Results.Json(results);
});
app.MapGet("/api/storefronts/v3/giftdropstore/3", async (HttpRequest request, AppDbContext db) =>
{
var storefronts = await storefrontService.GetStorefrontsAsync();
var storefront = storefronts.FirstOrDefault(s => s.StorefrontType == 2 && s.Name == "watch_store");
if (storefront == null)
{
var json = File.ReadAllText("JSON/storefront3.json");
return Results.Content(json, "application/json");
}
var storeItems = storefront.Items.Select(item => new
{
item.Id,
item.StorefrontId,
item.PurchasableItemId,
item.Type,
item.IsFeatured,
item.NewUntil,
GiftDrops = item.GiftDrops.Select(gd => new
{
gd.Id,
gd.StorefrontItemId,
gd.GiftDropId,
gd.FriendlyName,
gd.Tooltip,
gd.ConsumableItemDesc,
gd.AvatarItemDesc,
gd.AvatarItemType,
gd.EquipmentPrefabName,
gd.EquipmentModificationGuid,
gd.IsQuery,
gd.Unique,
gd.SubscribersOnly,
gd.Level,
gd.Rarity,
gd.CurrencyType,
gd.Currency,
gd.Context,
gd.ItemSetId,
gd.ItemSetFriendlyName
}).ToList(),
Prices = item.Prices.Select(p => new
{
p.Id,
p.StorefrontItemId,
p.CurrencyType,
p.Price
}).ToList()
}).ToList();
var response = new { storefront.Id, storefront.Name, storefront.StorefrontType, storefront.NextUpdate, StoreItems = storeItems };
return Results.Ok(response);
});
app.MapGet("/api/challenge/v2/getCurrent", async (HttpRequest request, AppDbContext db) =>
{
var json = File.ReadAllText("JSON/weeklychallenge.json");
return Results.Content(json, "application/json");
//return "{}";
});
app.MapGet("/roomserver/rooms/bulk", async (HttpRequest request, AppDbContext db) =>
{
var idParam = request.Query["id"].FirstOrDefault();
@@ -1068,13 +918,6 @@ public class APIController
return Results.Json(response);
});
app.MapGet("/roomserver/rooms/createdby/me", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
var json = File.ReadAllText("JSON/ownedrooms.json");
return Results.Content(json, "application/json");
});
app.MapGet("/roomserver/rooms/{id}", async (HttpRequest request, AppDbContext db, string id) =>
{
// this sucked to do
@@ -90,9 +90,45 @@ public class AuthController
});
app.MapGet("/role/developer/{id}", async (HttpRequest request, AppDbContext db, string id) =>
{
// TODO: implement
return Results.Ok(RecNetResult.Ok());
});
app.MapPost("/api/accounts/v1/forplatformids", async (HttpRequest request, AppDbContext db) =>
{
request.EnableBuffering();
request.Body.Position = 0;
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var ids = new List<string>();
if (!string.IsNullOrWhiteSpace(body))
{
foreach (var pair in body.Split('&'))
{
// TODO: implement
return Results.Ok(RecNetResult.Ok());
});
var keyValue = pair.Split('=');
if (keyValue.Length == 2 && keyValue[0] == "Ids")
{
var idString = Uri.UnescapeDataString(keyValue[1]);
ids = idString.Split(',').ToList();
break;
}
}
}
var results = new List<object>();
foreach (var platformId in ids)
{
var cachedLogin = await db.CachedLogins.FirstOrDefaultAsync(c => c.PlatformID == platformId);
if (cachedLogin != null)
{
results.Add(new { accountId = cachedLogin.AccountId, platformId = platformId });
}
}
return Results.Json(results);
});
}
}
@@ -12,6 +12,7 @@ public class EconController
{
var jwtService = app.Services.GetRequiredService<JwtTokenService>();
var notificationService = app.Services.GetRequiredService<NotificationService>();
var storefrontService = app.Services.GetRequiredService<StorefrontFillService>();
app.MapGet("/api/avatar/v1/defaultunlocked", () =>
{
@@ -362,5 +363,133 @@ public class EconController
return Results.Json(balance);
});
app.MapGet("/api/storefronts/v3/giftdropstore/3", async (HttpRequest request, AppDbContext db) =>
{
var storefronts = await storefrontService.GetStorefrontsAsync();
var storefront = storefronts.FirstOrDefault(s => s.StorefrontType == 2 && s.Name == "watch_store");
if (storefront == null)
{
var json = File.ReadAllText("JSON/storefront3.json");
return Results.Content(json, "application/json");
}
var storeItems = storefront.Items.Select(item => new
{
item.Id,
item.StorefrontId,
item.PurchasableItemId,
item.Type,
item.IsFeatured,
item.NewUntil,
GiftDrops = item.GiftDrops.Select(gd => new
{
gd.Id,
gd.StorefrontItemId,
gd.GiftDropId,
gd.FriendlyName,
gd.Tooltip,
gd.ConsumableItemDesc,
gd.AvatarItemDesc,
gd.AvatarItemType,
gd.EquipmentPrefabName,
gd.EquipmentModificationGuid,
gd.IsQuery,
gd.Unique,
gd.SubscribersOnly,
gd.Level,
gd.Rarity,
gd.CurrencyType,
gd.Currency,
gd.Context,
gd.ItemSetId,
gd.ItemSetFriendlyName
}).ToList(),
Prices = item.Prices.Select(p => new
{
p.Id,
p.StorefrontItemId,
p.CurrencyType,
p.Price
}).ToList()
}).ToList();
var response = new { storefront.Id, storefront.Name, storefront.StorefrontType, storefront.NextUpdate, StoreItems = storeItems };
return Results.Ok(response);
});
app.MapGet("/api/challenge/v2/getCurrent", async (HttpRequest request, AppDbContext db) =>
{
var json = File.ReadAllText("JSON/weeklychallenge.json");
return Results.Content(json, "application/json");
//return "{}";
});
app.MapGet("/api/avatar/v3/saved", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
request.EnableBuffering();
var items = await db.SavedOutfits
.Where(i => i.OwnerAccountId == id)
.ToListAsync();
return Results.Json(items);
});
app.MapGet("/api/avatar/v2/gifts", async (HttpRequest request, AppDbContext db, JwtTokenService jwtService) =>
{
try
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
// Get all pending (unconsumed) gifts for this player
var pendingGifts = await db.ReceivedGifts
.Where(rg => rg.ReceiverAccountId == id && !rg.IsConsumed)
.ToListAsync();
return Results.Json(pendingGifts);
}
catch (Exception ex)
{
return Results.Problem($"Error retrieving gifts: {ex.Message}");
}
});
app.MapGet("/api/gamerewards/v1/pending", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
return "[]";
});
app.MapGet("/api/roomkeys/v1/mine", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
return "[]";
});
app.MapPost("/api/CampusCard/v1/UpdateAndGetSubscription", async (HttpRequest request, AppDbContext db) =>
{
return Results.Json(new { subscription = (object?)null, platformAccountSubscribedPlayerId = (object?)null });
});
}
}
@@ -1,14 +1,26 @@
using System.Net.Mime;
using CannedNet.Services.Infrastructure;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace CannedNet.Services.Controllers;
public class ImageController
{
private static readonly HttpClient HttpClient = new();
private static readonly byte[] PlaceholderJpeg;
static ImageController()
{
Signatures.Init();
using var image = new Image<Rgba32>(64, 64);
image.Mutate(x => x.BackgroundColor(Color.FromRgb(32, 32, 32)));
using var ms = new MemoryStream();
image.SaveAsJpeg(ms, new JpegEncoder { Quality = 50 });
PlaceholderJpeg = ms.ToArray();
}
public WebApplicationBuilder Initialize(string[]? args = null)
=> ServiceExtensions.CreateRecNetBuilder(args);
@@ -16,67 +28,84 @@ public class ImageController
public void MapEndpoints(WebApplication app)
{
var imagesDir = Path.Combine("Images");
Directory.CreateDirectory(imagesDir);
app.MapGet("/{imageName}", async (
HttpContext context,
string imageName
) =>
app.MapGet("/{imageName}", async (HttpContext context, string imageName) =>
{
byte[] imageBytes;
var filePath = Path.Combine(imagesDir, imageName);
byte[] imageBytes;
string contentType;
if (File.Exists(filePath))
{
imageBytes = await File.ReadAllBytesAsync(filePath);
var ext = Path.GetExtension(imageName).ToLowerInvariant();
contentType = ext switch
{
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".webp" => "image/webp",
".bmp" => "image/bmp",
_ => "image/png"
};
}
else
{
var response = await HttpClient.GetAsync(
$"https://cdn.rec.net/img/{imageName}"
);
if (!response.IsSuccessStatusCode)
return Results.NotFound();
imageBytes = await response.Content.ReadAsByteArrayAsync();
imageBytes = PlaceholderJpeg;
contentType = "image/jpeg";
}
int? width = null;
int? height = null;
var cropSquare = context.Request.Query["cropSquare"].FirstOrDefault();
var widthStr = context.Request.Query["width"].FirstOrDefault();
var heightStr = context.Request.Query["height"].FirstOrDefault();
if (int.TryParse(context.Request.Query["width"], out var w))
width = w;
if (int.TryParse(context.Request.Query["height"], out var h))
height = h;
if (width == null && height == null)
if (string.IsNullOrEmpty(widthStr) && string.IsNullOrEmpty(heightStr) && string.IsNullOrEmpty(cropSquare))
{
return Results.File(imageBytes, MediaTypeNames.Image.Png);
SignImageResponse(context, ref imageBytes);
return Results.File(imageBytes, contentType);
}
using var image = Image.Load(imageBytes);
image.Mutate(x => x.Resize(new ResizeOptions
var resizeWidth = 0;
var resizeHeight = 0;
if (!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false")
{
Size = new Size(
width ?? 0,
height ?? 0
),
Mode = ResizeMode.Max
}));
var size = Math.Min(image.Width, image.Height);
var x = (image.Width - size) / 2;
var y = (image.Height - size) / 2;
image.Mutate(img => img.Crop(new Rectangle(x, y, size, size)));
}
await using var output = new MemoryStream();
if (int.TryParse(widthStr, out var w))
resizeWidth = w;
await image.SaveAsync(output, new PngEncoder());
if (int.TryParse(heightStr, out var h))
resizeHeight = h;
return Results.File(
output.ToArray(),
MediaTypeNames.Image.Png
);
if (resizeWidth > 0 || resizeHeight > 0)
{
image.Mutate(x => x.Resize(resizeWidth, resizeHeight));
}
using var output = new MemoryStream();
await image.SaveAsJpegAsync(output, new JpegEncoder { Quality = 85 });
imageBytes = output.ToArray();
SignImageResponse(context, ref imageBytes);
return Results.File(imageBytes, "image/jpeg");
});
}
private static void SignImageResponse(HttpContext context, ref byte[] imageBytes)
{
if (context.Request.Query["sig"] != "p1") return;
var signature = Signatures.Sign(imageBytes);
if (signature != null)
{
context.Response.Headers["Content-Signature"] = $"key-id=KEY:RSA:p1.rec.net; data={signature}";
}
}
}
@@ -231,7 +231,157 @@ public class MatchmakingController
return Results.Json(response);
});
app.MapPost("/goto/none", (HttpRequest request) =>
app.MapPost("/matchmake/{room}", async (HttpRequest request, string room, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
request.EnableBuffering();
request.Body.Position = 0;
var form = await request.ReadFormAsync();
int joinMode = 0;
if (int.TryParse(form["JoinMode"].ToString(), out var mode))
{
joinMode = mode;
}
request.Body.Position = 0;
Room? roomData = null;
var roomLower = room.ToLower();
if (roomLower == "dorm")
{
roomData = await db.Rooms.FirstOrDefaultAsync(r => r.IsDorm && r.CreatorAccountId == id);
}
else if (int.TryParse(room, out var roomId))
{
roomData = await db.Rooms.FirstOrDefaultAsync(r => r.RoomId == roomId);
}
if (roomData == null)
{
roomData = await db.Rooms.FirstOrDefaultAsync(r => r.Name.ToLower() == roomLower);
}
if (roomData == null)
{
return Results.NotFound("Room not found");
}
var subRoom = await db.SubRooms.FirstOrDefaultAsync(s => s.RoomId == roomData.RoomId);
string location = subRoom?.UnitySceneId ?? "";
int subRoomId = subRoom?.SubRoomId ?? 0;
int maxCapacity = subRoom?.MaxPlayers ?? 4;
RoomInstance? instanceToUse;
string photonRoomId;
bool isPrivateInstance = joinMode == 2 || roomData.IsDorm;
var existingInstance = await db.RoomInstances.FirstOrDefaultAsync(r => r.OwnerAccountId == id && r.roomId == roomData.RoomId);
if (existingInstance != null)
{
photonRoomId = existingInstance.photonRoomId ?? Guid.NewGuid().ToString();
existingInstance.roomInstanceId = existingInstance.Id;
existingInstance.roomId = roomData.RoomId;
existingInstance.subRoomId = subRoomId;
existingInstance.location = location;
existingInstance.dataBlob = "";
existingInstance.photonRegionId = "us";
existingInstance.photonRoomId = photonRoomId;
existingInstance.name = roomData.Name;
existingInstance.maxCapacity = maxCapacity;
existingInstance.isFull = false;
existingInstance.isPrivate = isPrivateInstance;
instanceToUse = existingInstance;
}
else
{
photonRoomId = Guid.NewGuid().ToString();
var oldInstances = await db.RoomInstances
.Where(r => r.OwnerAccountId == id && r.roomId != roomData.RoomId)
.ToListAsync();
db.RoomInstances.RemoveRange(oldInstances);
var publicInstance = await db.RoomInstances
.FirstOrDefaultAsync(r => r.roomId == roomData.RoomId && !r.isPrivate && !r.isFull);
if (publicInstance != null)
{
photonRoomId = publicInstance.photonRoomId ?? Guid.NewGuid().ToString();
}
instanceToUse = new RoomInstance
{
OwnerAccountId = id,
roomInstanceId = 1,
roomId = roomData.RoomId,
subRoomId = subRoomId,
roomInstanceType = 2,
location = location,
dataBlob = "",
photonRegionId = "us",
photonRoomId = photonRoomId,
name = roomData.Name,
maxCapacity = maxCapacity,
isFull = false,
isPrivate = isPrivateInstance,
isInProgress = false,
EncryptVoiceChat = roomData.EncryptVoiceChat
};
db.RoomInstances.Add(instanceToUse);
}
await db.SaveChangesAsync();
if (instanceToUse.Id == 0)
{
instanceToUse = await db.RoomInstances.FirstOrDefaultAsync(r => r.OwnerAccountId == id && r.roomId == roomData.RoomId);
}
var response = new
{
errorCode = 0,
roomInstance = new
{
roomInstanceId = instanceToUse?.Id ?? 1,
roomId = instanceToUse?.roomId ?? roomData.RoomId,
subRoomId = instanceToUse?.subRoomId ?? subRoomId,
roomInstanceType = instanceToUse?.roomInstanceType ?? 2,
location = instanceToUse?.location ?? location,
dataBlob = instanceToUse?.dataBlob ?? "",
eventId = instanceToUse?.eventId ?? 0,
clubId = instanceToUse?.clubId ?? 0,
roomCode = instanceToUse?.roomCode ?? "",
photonRegionId = instanceToUse?.photonRegionId ?? "us",
photonRoomId = instanceToUse?.photonRoomId ?? photonRoomId,
name = instanceToUse?.name ?? roomData.Name,
maxCapacity = instanceToUse?.maxCapacity ?? maxCapacity,
isFull = instanceToUse?.isFull ?? false,
isPrivate = isPrivateInstance,
isInProgress = instanceToUse?.isInProgress ?? false,
EncryptVoiceChat = instanceToUse?.EncryptVoiceChat ?? roomData.EncryptVoiceChat
}
};
return Results.Json(response);
});
app.MapPost("/matchmake/none", (HttpRequest request) =>
{
// dormroom location id : 76d98498-60a1-430c-ab76-b54a29b7a163
// offline dorm
@@ -261,11 +411,6 @@ public class MatchmakingController
});
});
app.MapPost("/matchmake/none", () =>
{
return Results.Json(new { errorCode = 0 });
});
app.MapPost("/player/heartbeat", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
@@ -45,6 +45,13 @@ public class RoomsController
return Results.Json(await BuildRoomResponse(result, db));
});
app.MapGet("/roomserver/rooms/createdby/me", async (HttpRequest request, AppDbContext db) =>
{
// TODO ADD FUNCTIONALITY
var json = File.ReadAllText("JSON/ownedrooms.json");
return Results.Content(json, "application/json");
});
}
private static async Task<object> BuildRoomResponse(Room result, AppDbContext db)
+46
View File
@@ -0,0 +1,46 @@
using System.Security.Cryptography;
using System.Text;
namespace CannedNet.Services;
public static class Signatures
{
private static RSA? _rsa;
private static bool _initialized;
public static void Init()
{
if (_initialized) return;
_initialized = true;
try
{
var pemPath = Path.Combine("Data", "PrivateKey.pem");
if (!File.Exists(pemPath))
{
Console.WriteLine("sigs - creating new sig key");
Directory.CreateDirectory("Data");
using var newKey = RSA.Create(2048);
var pem = newKey.ExportPkcs8PrivateKeyPem();
File.WriteAllText(pemPath, pem);
Console.WriteLine("sigs - key saved to " + pemPath);
}
var pemContent = File.ReadAllText(pemPath);
_rsa = RSA.Create();
_rsa.ImportFromPem(pemContent.ToCharArray());
Console.WriteLine("sigs - key loaded");
}
catch (Exception ex)
{
Console.WriteLine($"sigs - failed to load key: {ex.Message}");
}
}
public static string? Sign(byte[] data)
{
if (_rsa == null) return null;
var signatureBytes = _rsa.SignData(data, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureBytes);
}
}