Skip to content

Commit

Permalink
wip: auth in blossom repo impl
Browse files Browse the repository at this point in the history
  • Loading branch information
leo-lox committed Jan 11, 2025
1 parent b91cee3 commit 35ad939
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 67 deletions.
105 changes: 75 additions & 30 deletions packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:typed_data';

import '../../../domain_layer/entities/nip_01_event.dart';
import '../../../domain_layer/repositories/blossom.dart';
import '../../data_sources/http_request.dart';

Expand All @@ -18,82 +19,119 @@ class BlossomRepositoryImpl implements BlossomRepository {
}

@override
Future<List<BlobUploadResult>> uploadBlob(
Uint8List data, {
Future<List<BlobUploadResult>> uploadBlob({
required Uint8List data,
required Nip01Event authorization,
String? contentType,
UploadStrategy strategy = UploadStrategy.mirrorAfterSuccess,
}) async {
switch (strategy) {
case UploadStrategy.mirrorAfterSuccess:
return _uploadWithMirroring(data, contentType);
return _uploadWithMirroring(
data: data,
contentType: contentType,
authorization: authorization,
);
case UploadStrategy.allSimultaneous:
return _uploadToAllServers(data, contentType);
return _uploadToAllServers(
data: data,
contentType: contentType,
authorization: authorization,
);
case UploadStrategy.firstSuccess:
return _uploadToFirstSuccess(data, contentType);
return _uploadToFirstSuccess(
data: data,
contentType: contentType,
authorization: authorization,
);
}
}

Future<List<BlobUploadResult>> _uploadWithMirroring(
Uint8List data,
Future<List<BlobUploadResult>> _uploadWithMirroring({
required Uint8List data,
required Nip01Event authorization,
String? contentType,
) async {
}) async {
final results = <BlobUploadResult>[];

// Try primary upload
final primaryResult = await _uploadToServer(
serverUrls.first,
data,
contentType,
serverUrl: serverUrls.first,
data: data,
contentType: contentType,
authorization: authorization,
);
results.add(primaryResult);

if (primaryResult.success) {
// Mirror to other servers
final mirrorResults = await Future.wait(serverUrls
.skip(1)
.map((url) => _uploadToServer(url, data, contentType)));
final mirrorResults =
await Future.wait(serverUrls.skip(1).map((url) => _uploadToServer(
serverUrl: url,
data: data,
contentType: contentType,
authorization: authorization,
)));
results.addAll(mirrorResults);
}

return results;
}

Future<List<BlobUploadResult>> _uploadToAllServers(
Uint8List data,
Future<List<BlobUploadResult>> _uploadToAllServers({
required Uint8List data,
required Nip01Event authorization,
String? contentType,
) async {
final results = await Future.wait(
serverUrls.map((url) => _uploadToServer(url, data, contentType)));
}) async {
final results = await Future.wait(serverUrls.map((url) => _uploadToServer(
serverUrl: url,
data: data,
contentType: contentType,
authorization: authorization,
)));
return results;
}

Future<List<BlobUploadResult>> _uploadToFirstSuccess(
Uint8List data,
Future<List<BlobUploadResult>> _uploadToFirstSuccess({
required Uint8List data,
required Nip01Event authorization,
String? contentType,
) async {
}) async {
for (final url in serverUrls) {
final result = await _uploadToServer(url, data, contentType);
final result = await _uploadToServer(
serverUrl: url,
data: data,
contentType: contentType,
authorization: authorization,
);
if (result.success) {
return [result];
}
}

// If all servers failed, return all errors
final results = await _uploadToAllServers(data, contentType);
final results = await _uploadToAllServers(
data: data,
contentType: contentType,
authorization: authorization,
);
return results;
}

Future<BlobUploadResult> _uploadToServer(
String serverUrl,
Uint8List data,
Future<BlobUploadResult> _uploadToServer({
required String serverUrl,
required Uint8List data,
Nip01Event? authorization,
String? contentType,
) async {
}) async {
try {
final response = await client.put(
url: Uri.parse('$serverUrl/upload'),
body: data,
headers: {
if (contentType != null) 'Content-Type': contentType,
if (authorization != null)
'Authorization': "Nostr ${authorization.toBase64()}",
'Content-Length': '${data.length}',
},
);
Expand Down Expand Up @@ -121,7 +159,10 @@ class BlossomRepositoryImpl implements BlossomRepository {
}

@override
Future<Uint8List> getBlob(String sha256) async {
Future<Uint8List> getBlob(
String sha256, {
Nip01Event? authorization,
}) async {
Exception? lastError;

for (final url in serverUrls) {
Expand All @@ -148,6 +189,7 @@ class BlossomRepositoryImpl implements BlossomRepository {
String pubkey, {
DateTime? since,
DateTime? until,
Nip01Event? authorization,
}) async {
Exception? lastError;

Expand Down Expand Up @@ -177,7 +219,10 @@ class BlossomRepositoryImpl implements BlossomRepository {
}

@override
Future<List<BlobDeleteResult>> deleteBlob(String sha256) async {
Future<List<BlobDeleteResult>> deleteBlob({
required String sha256,
required Nip01Event authorization,
}) async {
final results = await Future.wait(
serverUrls.map((url) => _deleteFromServer(url, sha256)));
return results;
Expand Down
5 changes: 5 additions & 0 deletions packages/ndk/lib/domain_layer/entities/nip_01_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ class Nip01Event {
};
}

/// Returns the Event object as a base64-encoded JSON string
String toBase64() {
return base64Encode(utf8.encode(json.encode(toJson())));
}

/// sign the event with given privateKey
/// [WARN] only for testing! Use [EventSigner] to sign events in production
void sign(String privateKey) {
Expand Down
25 changes: 19 additions & 6 deletions packages/ndk/lib/domain_layer/repositories/blossom.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:typed_data';

import 'package:ndk/domain_layer/entities/nip_01_event.dart';

enum UploadStrategy {
/// Upload to first server, then mirror to others
mirrorAfterSuccess,
Expand All @@ -13,21 +15,32 @@ enum UploadStrategy {

abstract class BlossomRepository {
/// Uploads a blob using the specified strategy
Future<List<BlobUploadResult>> uploadBlob(
Uint8List data, {
Future<List<BlobUploadResult>> uploadBlob({
required Uint8List data,
required Nip01Event authorization,
String? contentType,
UploadStrategy strategy = UploadStrategy.mirrorAfterSuccess,
});

/// Gets a blob by trying servers sequentially until success
Future<Uint8List> getBlob(String sha256);
Future<Uint8List> getBlob(
String sha256, {
Nip01Event? authorization,
});

/// Lists blobs from the first successful server
Future<List<BlobDescriptor>> listBlobs(String pubkey,
{DateTime? since, DateTime? until});
Future<List<BlobDescriptor>> listBlobs(
String pubkey, {
DateTime? since,
DateTime? until,
Nip01Event? authorization,
});

/// Attempts to delete blob from all servers
Future<List<BlobDeleteResult>> deleteBlob(String sha256);
Future<List<BlobDeleteResult>> deleteBlob({
required String sha256,
required Nip01Event authorization,
});
}

class BlobDescriptor {
Expand Down
39 changes: 39 additions & 0 deletions packages/ndk/lib/domain_layer/usecases/files/blossom.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'dart:typed_data';

import '../../repositories/blossom.dart';

/// direct access usecase to blossom \
/// use files usecase for a more convinent way to manage files
class Blossom {
final BlossomRepository repository;

Blossom(this.repository);

Future<List<BlobUploadResult>> uploadBlob({
required Uint8List data,
String? contentType,
UploadStrategy strategy = UploadStrategy.mirrorAfterSuccess,
}) {
return repository.uploadBlob(
data,
contentType: contentType,
strategy: strategy,
);
}

Future<Uint8List> getBlob(String sha256) {
return repository.getBlob(sha256);
}

Future<List<BlobDescriptor>> listBlobs(
String pubkey, {
DateTime? since,
DateTime? until,
}) {
return repository.listBlobs(pubkey, since: since, until: until);
}

Future<void> delteBlob(String sha256) {
return repository.deleteBlob(sha256);
}
}
31 changes: 1 addition & 30 deletions packages/ndk/lib/domain_layer/usecases/files/files.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,9 @@ import 'dart:typed_data';

import '../../repositories/blossom.dart';

/// high level usecase to manage files on nostr
class Files {
final BlossomRepository repository;

Files(this.repository);

Future<List<BlobUploadResult>> uploadBlob({
required Uint8List data,
String? contentType,
UploadStrategy strategy = UploadStrategy.mirrorAfterSuccess,
}) {
return repository.uploadBlob(
data,
contentType: contentType,
strategy: strategy,
);
}

Future<Uint8List> getBlob(String sha256) {
return repository.getBlob(sha256);
}

Future<List<BlobDescriptor>> listBlobs(
String pubkey, {
DateTime? since,
DateTime? until,
}) {
return repository.listBlobs(pubkey, since: since, until: until);
}

// lib/domain/usecases/delete_blob_usecase.dart

Future<void> delteBlob(String sha256) {
return repository.deleteBlob(sha256);
}
}
4 changes: 3 additions & 1 deletion packages/ndk/test/mocks/mock_blossom_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ class MockBlossomServer {
}

try {
final authEvent = json.decode(authHeader);
final authEvent =
json.decode(utf8.decode(base64Decode(authHeader.split(' ')[1])));

if (!_verifyAuthEvent(authEvent, 'delete')) {
return Response.forbidden('Invalid authorization event');
}
Expand Down

0 comments on commit 35ad939

Please sign in to comment.