feat(api): bind encrypted workspaces to accounts and devices
This commit is contained in:
1 parent
019b15041a
commit
c7d8ef15bc
49 files changed
+1904
-278
No files matched your search
@@ -10,8 +10,9 @@ public sealed class ShareService(
|
||||
ServerLimits limits,
|
||||
int draftTtlSeconds)
|
||||
{
|
||||
public ShareInfo Create(CreateShareRequest req, byte[]? creatorEndpointHash)
|
||||
public ShareInfo Create(CreateShareRequest req, byte[]? creatorEndpointHash, string creatorAccount)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(creatorAccount)) throw new WebDropException(ErrorCode.AccountRequired);
|
||||
if (!Id16.TryParse(req.Id, out var id)
|
||||
|| !Codec.TryDecodeExact(req.ReadHash, TokenHash.Size, out var readHash)
|
||||
|| !Codec.TryDecodeExact(req.OwnerHash, TokenHash.Size, out var ownerHash))
|
||||
@@ -21,7 +22,7 @@ public sealed class ShareService(
|
||||
settings.Validate(limits);
|
||||
long now = clock.NowMs;
|
||||
var share = new Share(id, settings, readHash, ownerHash, creatorEndpointHash, now,
|
||||
now + Math.Max(60, draftTtlSeconds) * 1000L, null, null, 0);
|
||||
now + Math.Max(60, draftTtlSeconds) * 1000L, null, null, 0, creatorAccount);
|
||||
|
||||
store.Write(tx =>
|
||||
{
|
||||
@@ -48,6 +49,7 @@ public sealed class ShareService(
|
||||
return ToInfo(share, tx.ListShareItems(id));
|
||||
});
|
||||
|
||||
|
||||
public ShareInfo UpdateSettings(Id16 id, Credentials creds, ShareSettingsPatch patch) => store.Write(tx =>
|
||||
{
|
||||
var share = Draft(tx.GetShare(id), creds);
|
||||
@@ -66,7 +68,7 @@ public sealed class ShareService(
|
||||
throw new WebDropException(ErrorCode.TooLarge);
|
||||
if (!env.IsEnc) throw new WebDropException(ErrorCode.ModeMismatch);
|
||||
if (!PayloadTiers.IsValidEncPayloadLength(env.PayloadLength)) throw new WebDropException(ErrorCode.BadRequest);
|
||||
if (env.IsSnapshot) throw new WebDropException(ErrorCode.BadRequest);
|
||||
if (env.IsSnapshot || env.IsControl) throw new WebDropException(ErrorCode.BadRequest);
|
||||
if (!env.IsGated && (env.FrameIndex != 0 || !env.IsFinal)) throw new WebDropException(ErrorCode.BadRequest);
|
||||
if (maxDownloads is < 0) throw new WebDropException(ErrorCode.BadRequest);
|
||||
|
||||
@@ -198,7 +200,7 @@ public sealed class ShareService(
|
||||
var frame = store.Write(tx =>
|
||||
{
|
||||
var share = SealedForRead(tx.GetShare(id), creds);
|
||||
RegisterVisitor(tx, share, creds.EndpointHash);
|
||||
RegisterVisitor(tx, share, creds.EndpointHash, creds.AccountName);
|
||||
var manifestId = share.ManifestItemId ?? throw new WebDropException(ErrorCode.NotFound);
|
||||
return tx.GetShareFrame(id, manifestId, 0) ?? throw new WebDropException(ErrorCode.Gone);
|
||||
});
|
||||
@@ -217,7 +219,7 @@ public sealed class ShareService(
|
||||
long now = clock.NowMs;
|
||||
if (item.CloseDeadline is { } deadline && (fromIndex == 0 || now >= deadline))
|
||||
throw new WebDropException(ErrorCode.Gone);
|
||||
RegisterVisitor(tx, share, creds.EndpointHash);
|
||||
RegisterVisitor(tx, share, creds.EndpointHash, creds.AccountName);
|
||||
if (fromIndex == 0)
|
||||
{
|
||||
if (item.MaxDownloads > 0 && item.Downloads >= item.MaxDownloads) throw new WebDropException(ErrorCode.Gone);
|
||||
@@ -310,6 +312,8 @@ public sealed class ShareService(
|
||||
|
||||
private void RequireOwner(Share share, Credentials creds)
|
||||
{
|
||||
if (share.CreatorAccount != creds.AccountName)
|
||||
throw new WebDropException(ErrorCode.AccountRequired);
|
||||
if (creds.ClaimedRole != Role.Owner || !TokenHash.Matches(creds.Token ?? [], share.OwnerHash))
|
||||
throw new WebDropException(ErrorCode.Unauthorized);
|
||||
}
|
||||
@@ -320,8 +324,9 @@ public sealed class ShareService(
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private void RegisterVisitor(IResourceTx tx, Share share, byte[] endpointHash)
|
||||
private void RegisterVisitor(IResourceTx tx, Share share, byte[] endpointHash, string? accountName)
|
||||
{
|
||||
if (accountName == share.CreatorAccount) return;
|
||||
if (share.CreatorEndpointHash is { } creator && CryptographicOperations.FixedTimeEquals(creator, endpointHash)) return;
|
||||
if (tx.HasShareVisitor(share.Id, endpointHash)) return;
|
||||
if (share.Settings.MaxVisitors > 0 && tx.CountShareVisitors(share.Id) >= share.Settings.MaxVisitors)
|
||||
@@ -360,6 +365,7 @@ public sealed class ShareService(
|
||||
ItemId = i.ItemId.ToBase64Url(),
|
||||
Bytes = i.Bytes,
|
||||
Frames = i.FrameCount,
|
||||
Complete = i.IsComplete,
|
||||
MaxDownloads = i.MaxDownloads,
|
||||
DownloadsLeft = i.MaxDownloads == 0 ? null : Math.Max(0, i.MaxDownloads - i.Downloads),
|
||||
}).ToList(),
|
||||
|
||||
Reference in new issue
Block a user