feat(api): add account workspaces and signed content events
This commit is contained in:
1 parent
937b774a93
commit
7b8d61d1ba
63 files changed
+5407
-370
No files matched your search
@@ -7,6 +7,19 @@ public static partial class Endpoints
|
||||
{
|
||||
private static void MapAccounts(RouteGroupBuilder api, SqliteAccountStore accounts)
|
||||
{
|
||||
api.MapGet("/accounts/profile", (HttpContext ctx) =>
|
||||
{
|
||||
if (!TryAccount(ctx, accounts, out var username)) throw new WebDropException(ErrorCode.AccountRequired);
|
||||
return Json(accounts.Profile(username), ApiJson.Default.AccountProfile);
|
||||
});
|
||||
api.MapPatch("/accounts/profile", async (HttpContext ctx, ChannelService svc, CancellationToken ct) =>
|
||||
{
|
||||
if (!TryAccount(ctx, accounts, out var username)) throw new WebDropException(ErrorCode.AccountRequired);
|
||||
var req = await ReadJson(ctx.Request, ApiJson.Default.AccountProfileRequest, ct);
|
||||
var profile = accounts.UpdateProfile(username, req.DisplayName, req.Avatar);
|
||||
svc.AccountProfileChanged(username);
|
||||
return Json(profile, ApiJson.Default.AccountProfile);
|
||||
});
|
||||
api.MapPost("/accounts", async (HttpContext ctx, CancellationToken ct) =>
|
||||
{
|
||||
var req = await ReadJson(ctx.Request, ApiJson.Default.AccountRegisterRequest, ct);
|
||||
@@ -15,7 +28,8 @@ public static partial class Endpoints
|
||||
var verifier = DecodeBase64(req.Verifier, 32, 32);
|
||||
var vault = DecodeBase64(req.Vault, 28, 1_300_000);
|
||||
ValidatePublicKey(req.PublicKey);
|
||||
if (!accounts.Register(username, salt, verifier, vault, req.PublicKey)) return Json(new ErrorBody { Error = "username_unavailable" }, ApiJson.Default.ErrorBody, StatusCodes.Status409Conflict);
|
||||
ValidatePublicKey(req.SigningPublicKey);
|
||||
if (!accounts.Register(username, salt, verifier, vault, req.PublicKey, req.SigningPublicKey)) return Json(new ErrorBody { Error = "username_unavailable" }, ApiJson.Default.ErrorBody, StatusCodes.Status409Conflict);
|
||||
return Results.NoContent();
|
||||
}).RequireRateLimiting(CreatePolicy);
|
||||
|
||||
@@ -59,6 +73,30 @@ public static partial class Endpoints
|
||||
if (req.Name is null || !accounts.RenameDevice(username, id, req.Name.Trim())) throw new WebDropException(ErrorCode.BadRequest);
|
||||
return Results.NoContent();
|
||||
});
|
||||
api.MapPut("/accounts/devices/signing-key", async (HttpContext ctx, SqliteAccountStore accountStore, CancellationToken ct) =>
|
||||
{
|
||||
if (!TryAccount(ctx, accountStore, out var username)) throw new WebDropException(ErrorCode.AccountRequired);
|
||||
var req = await ReadJson(ctx.Request, ApiJson.Default.AccountDeviceSigningRequest, ct);
|
||||
var token = ctx.Request.Headers["X-WD-Account"].ToString();
|
||||
var deviceId = accountStore.SessionDevice(token);
|
||||
if (deviceId is null || !string.Equals(deviceId, req.DeviceId, StringComparison.OrdinalIgnoreCase)) throw new WebDropException(ErrorCode.Forbidden);
|
||||
ValidatePublicKey(req.PublicKey);
|
||||
var rootKey = accountStore.GetSigningPublicKey(username);
|
||||
if (rootKey is null) throw new WebDropException(ErrorCode.BadRequest);
|
||||
using var root = System.Text.Json.JsonDocument.Parse(rootKey);
|
||||
var rootObject = root.RootElement;
|
||||
if (!Codec.TryDecodeExact(rootObject.GetProperty("x").GetString(), 32, out var rootX)
|
||||
|| !Codec.TryDecodeExact(rootObject.GetProperty("y").GetString(), 32, out var rootY)) throw new WebDropException(ErrorCode.BadRequest);
|
||||
using var device = System.Text.Json.JsonDocument.Parse(req.PublicKey);
|
||||
var deviceObject = device.RootElement;
|
||||
if (!Codec.TryDecodeExact(deviceObject.GetProperty("x").GetString(), 32, out var deviceX)
|
||||
|| !Codec.TryDecodeExact(deviceObject.GetProperty("y").GetString(), 32, out var deviceY)) throw new WebDropException(ErrorCode.BadRequest);
|
||||
var payload = SignatureCodec.DeviceCertificatePayload(username, deviceId, deviceX, deviceY);
|
||||
var signature = DecodeBase64(req.Signature, 64, 64);
|
||||
if (!SignatureCodec.VerifyP256(rootX, rootY, "device.certificate", payload, signature)) throw new WebDropException(ErrorCode.Forbidden);
|
||||
if (!accountStore.SaveDeviceSigningKey(username, deviceId, req.PublicKey, signature)) throw new WebDropException(ErrorCode.Forbidden);
|
||||
return Results.NoContent();
|
||||
});
|
||||
api.MapDelete("/accounts/devices/{id}", (string id, HttpContext ctx, ChannelService svc) =>
|
||||
{
|
||||
if (!TryAccount(ctx, accounts, out var username)) throw new WebDropException(ErrorCode.AccountRequired);
|
||||
|
||||
Reference in new issue
Block a user