Skip to content

Commit

Permalink
feature/web-create (#56)
Browse files Browse the repository at this point in the history
* removed duplicate decoded jwt

* updated keymanager class to contain all helper methods

* added missing did resolution results

* updated portable did naming convention to use map instead of json

* added web create method and updated the resolver and tests

* missing classes

* added per verification method private key creation

* combined did resolution result error factorys into single

* reverted bearer

* reverted key manager

* reverted in memory key manager

* updated web create

* redo part of an undo
  • Loading branch information
fingersonfire authored Apr 3, 2024
1 parent eebc8a9 commit c2d7046
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 80 deletions.
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
17 changes: 17 additions & 0 deletions packages/web5/lib/src/dids/did_core/did_verification_method.dart
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,
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

0 comments on commit c2d7046

Please sign in to comment.