Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/web-create #56

Merged
2 changes: 2 additions & 0 deletions packages/web5/lib/src/dids/bearer_did.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ class BearerDid {
String uri;
KeyManager keyManager;
DidDocument document;
DidDocumentMetadata metadata;

BearerDid({
required this.uri,
required this.keyManager,
required this.document,
this.metadata = const DidDocumentMetadata(),
});

Future<PortableDid> export() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class DidDocumentMetadata {
/// the scope of the containing DID document.
final String? canonicalId;

DidDocumentMetadata({
const DidDocumentMetadata({
this.created,
this.updated,
this.deactivated,
Expand Down
34 changes: 29 additions & 5 deletions packages/web5/lib/src/dids/did_core/did_resolution_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@ import 'package:web5/src/dids/did_core/did_document.dart';
import 'package:web5/src/dids/did_core/did_document_metadata.dart';
import 'package:web5/src/dids/did_core/did_resolution_metadata.dart';

enum DidResolutionError {
invalidDid,
notFound,
representationNotSupported,
}

extension ResolutionErrorValue on DidResolutionError {
String get value {
switch (this) {
case DidResolutionError.invalidDid:
return 'invalidDid';
case DidResolutionError.notFound:
return 'notFound';
case DidResolutionError.representationNotSupported:
return 'representationNotSupported';
default:
return 'unknown';
}
}
}

/// A class representing the result of a DID (Decentralized Identifier)
/// resolution.
///
Expand Down Expand Up @@ -61,13 +82,16 @@ class DidResolutionResult {
didResolutionMetadata ?? DidResolutionMetadata(),
didDocumentMetadata = didDocumentMetadata ?? DidDocumentMetadata();

/// A convenience constructor for creating a [DidResolutionResult] representing
/// A factory constructor for creating a [DidResolutionResult] representing
/// an invalid DID scenario. This sets the resolution metadata error to 'invalidDid'
/// and leaves the DID document as `null`.
DidResolutionResult.invalidDid()
: didResolutionMetadata = DidResolutionMetadata(error: 'invalidDid'),
didDocument = null,
didDocumentMetadata = DidDocumentMetadata();
factory DidResolutionResult.withError(DidResolutionError err) {
return DidResolutionResult(
didResolutionMetadata: DidResolutionMetadata(error: err.value),
didDocument: null,
didDocumentMetadata: DidDocumentMetadata(),
);
}

/// Converts this [DidResolutionResult] instance to a JSON map.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:web5/src/crypto.dart';
import 'package:web5/src/dids/did_core/did_resource.dart';
import 'package:web5/src/dids/did_core/did_verification_relationship.dart';

/// A DID document can express verification methods, such as cryptographic
/// public keys, which can be used to authenticate or authorize interactions
Expand Down Expand Up @@ -52,3 +53,19 @@ class DidVerificationMethod implements DidResource {
);
}
}

class DidCreateVerificationMethod {
DidCreateVerificationMethod({
required this.algorithm,
required this.controller,
this.id,
required this.purposes,
required this.type,
});

final AlgorithmId algorithm;
final String controller;
final String? id;
final List<VerificationPurpose> purposes;
final String type;
}
8 changes: 4 additions & 4 deletions packages/web5/lib/src/dids/did_dht/did_dht.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DidDht {
String relayUrl = 'https://diddht.tbddev.org',
}) async {
if (did.method != methodName) {
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final parsedRelayUrl = Uri.parse(relayUrl);
Expand All @@ -42,7 +42,7 @@ class DidDht {
// final seq = bytes.sublist(64, 72);

if (bytes.length < 72) {
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final v = bytes.sublist(72);
Expand All @@ -68,7 +68,7 @@ class DidDht {

if (rootRecord == null) {
// TODO: figure out more appopriate resolution error to use.
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final Map<String, List<String>> relationshipsMap = {};
Expand All @@ -77,7 +77,7 @@ class DidDht {

if (splitEntry.length != 2) {
// TODO: figure out more appopriate resolution error to use.
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final [property, values] = splitEntry;
Expand Down
10 changes: 5 additions & 5 deletions packages/web5/lib/src/dids/did_jwk/did_jwk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,31 @@ class DidJwk {
/// an invalid [DidResolutionResult].
///
/// Throws [FormatException] if the JWK parsing fails.
static Future<DidResolutionResult> resolve(Did did) {
static Future<DidResolutionResult> resolve(Did did) async {
if (did.method != methodName) {
return Future.value(DidResolutionResult.invalidDid());
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final dynamic jwk;

try {
jwk = json.fromBase64Url(did.id);
} on FormatException {
return Future.value(DidResolutionResult.invalidDid());
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final Jwk parsedJwk;

try {
parsedJwk = Jwk.fromJson(jwk);
} on Exception {
return Future.value(DidResolutionResult.invalidDid());
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

final didDocument = _createDidDocument(did, parsedJwk);
final didResolutionResult = DidResolutionResult(didDocument: didDocument);

return Future.value(didResolutionResult);
return didResolutionResult;
}

static DidDocument _createDidDocument(Did did, Jwk jwk) {
Expand Down
4 changes: 3 additions & 1 deletion packages/web5/lib/src/dids/did_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class DidResolver {
try {
did = Did.parse(uri);
} catch (e) {
return Future.value(DidResolutionResult.invalidDid());
return Future.value(
DidResolutionResult.withError(DidResolutionError.invalidDid),
);
}

final resolver = methodResolvers[did.method];
Expand Down
118 changes: 95 additions & 23 deletions packages/web5/lib/src/dids/did_web/did_web.dart
Original file line number Diff line number Diff line change
@@ -1,49 +1,121 @@
import 'dart:convert';
import 'dart:io';

import 'package:web5/src/crypto.dart';
import 'package:web5/src/dids/bearer_did.dart';
import 'package:web5/src/dids/did.dart';
import 'package:web5/src/dids/did_core.dart';
import 'package:web5/src/dids/did_method_resolver.dart';
import 'package:web5/src/dids/did.dart';

class DidWeb {
static const String methodName = 'web';
static final resolver = DidMethodResolver(name: methodName, resolve: resolve);

static final DidMethodResolver resolver = DidMethodResolver(
name: methodName,
resolve: resolve,
);

static Future<BearerDid> create({
required String url,
AlgorithmId? algorithm,
KeyManager? keyManager,
List<String>? alsoKnownAs,
List<String>? controllers,
List<DidService>? services,
List<DidCreateVerificationMethod>? verificationMethods,
fingersonfire marked this conversation as resolved.
Show resolved Hide resolved
DidDocumentMetadata? metadata,
}) async {
algorithm ??= AlgorithmId.ed25519;
keyManager ??= InMemoryKeyManager();

final parsed = Uri.tryParse(url);
if (parsed == null) throw 'Unable to parse url $url';
final String didId =
'did:web:${parsed.host}${parsed.pathSegments.join(':')}';

final DidDocument doc = DidDocument(
id: didId,
alsoKnownAs: alsoKnownAs,
controller: controllers ?? didId,
);

final List<DidCreateVerificationMethod> defaultMethods = [
DidCreateVerificationMethod(
algorithm: algorithm,
id: '0',
type: 'JsonWebKey',
controller: didId,
purposes: [
VerificationPurpose.authentication,
VerificationPurpose.assertionMethod,
VerificationPurpose.capabilityDelegation,
VerificationPurpose.capabilityInvocation,
],
),
];

final List<DidCreateVerificationMethod> methodsToAdd =
verificationMethods ?? defaultMethods;

for (final DidCreateVerificationMethod vm in methodsToAdd) {
final String alias = await keyManager.generatePrivateKey(vm.algorithm);
final Jwk publicKey = await keyManager.getPublicKey(alias);

final String methodId = '$didId#${vm.id}';
doc.addVerificationMethod(
DidVerificationMethod(
id: methodId,
type: vm.type,
controller: vm.controller,
publicKeyJwk: publicKey,
),
);

for (final VerificationPurpose purpose in vm.purposes) {
doc.addVerificationPurpose(purpose, methodId);
}
}

for (final DidService service in (services ?? [])) {
doc.addService(service);
}

return BearerDid(
uri: didId,
keyManager: keyManager,
document: doc,
metadata: metadata ?? DidDocumentMetadata(),
);
}

static Future<DidResolutionResult> resolve(
Did did, {
HttpClient? client,
}) async {
if (did.method != methodName) {
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.invalidDid);
}

// TODO: http technically not supported. remove after temp use
var resolutionUrl = did.id.replaceAll(':', '/');
if (resolutionUrl.contains('localhost')) {
resolutionUrl = 'http://$resolutionUrl';
} else {
resolutionUrl = 'https://$resolutionUrl';
}
final String documentUrl = Uri.decodeFull(did.id.replaceAll(':', '/'));
Uri? didUri = Uri.tryParse('https://$documentUrl');

if (Uri.parse(resolutionUrl).path.isEmpty) {
resolutionUrl = '$resolutionUrl/.well-known';
}
if (didUri == null) throw 'Unable to parse DID document Url $documentUrl';

resolutionUrl = Uri.decodeFull('$resolutionUrl/did.json');
final parsedUrl = Uri.parse(resolutionUrl);
// If none was specified, use the default path.
if (didUri.path.isEmpty) didUri = didUri.replace(path: '/.well-known');
didUri = didUri.replace(pathSegments: [...didUri.pathSegments, 'did.json']);

final httpClient = client ??= HttpClient();
final request = await httpClient.getUrl(parsedUrl);
final response = await request.close();
final HttpClient httpClient = client ??= HttpClient();
final HttpClientRequest request = await httpClient.getUrl(didUri);
final HttpClientResponse response = await request.close();

if (response.statusCode != 200) {
// TODO: change this to something more appropriate
return DidResolutionResult.invalidDid();
return DidResolutionResult.withError(DidResolutionError.notFound);
}

final str = await response.transform(utf8.decoder).join();
final jsonParsed = json.decode(str);
final doc = DidDocument.fromJson(jsonParsed);
final String str = await response.transform(utf8.decoder).join();
final dynamic jsonParsed = json.decode(str);
final DidDocument doc = DidDocument.fromJson(jsonParsed);

return DidResolutionResult(didDocument: doc);
}
Expand Down
10 changes: 5 additions & 5 deletions packages/web5/lib/src/dids/portable_did.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ class PortableDid {
List<Jwk>? privateKeys,
}) : privateKeys = privateKeys ?? [];

factory PortableDid.fromJson(Map<String, dynamic> json) {
factory PortableDid.fromMap(Map<String, dynamic> map) {
return PortableDid(
uri: json['uri'],
document: DidDocument.fromJson(json['document']),
uri: map['uri'],
document: DidDocument.fromJson(map['document']),
privateKeys:
(json['privateKeys'] as List).map((e) => Jwk.fromJson(e)).toList(),
(map['privateKeys'] as List).map((e) => Jwk.fromJson(e)).toList(),
);
}

Map<String, dynamic> toJson() {
Map<String, dynamic> get map {
return {
'uri': uri,
'document': document.toJson(),
Expand Down
26 changes: 0 additions & 26 deletions packages/web5/lib/src/jwt/jwt_decoded.dart

This file was deleted.

Loading
Loading