Skip to content

Commit

Permalink
switched to structs + interface for errors
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed Mar 1, 2025
1 parent e35a099 commit 430f8a3
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 63 deletions.
125 changes: 71 additions & 54 deletions Contrib/Auth/Did/DidAuth.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Basis.Contrib.Auth.DecentralizedIds;
using Basis.Contrib.Auth.DecentralizedIds.Newtypes;
using Basis.Contrib.Auth.DecentralizedIds.Result;
using CryptoRng = System.Security.Cryptography.RandomNumberGenerator;
using Debug = System.Diagnostics.Debug;
using Empty = System.ValueTuple;

namespace Basis.Contrib.Auth.DecentralizedIds
{
Expand All @@ -25,47 +22,58 @@ public sealed record Config
};
}

/// Errors during resolution of DID Document.
public abstract record DidResolveErr : DidVerifyErr
public readonly struct DidResolveErr : IDidVerifyErr
{
private DidResolveErr() { }
public readonly E e;

/// Did had an invalid prefix.
public sealed record InvalidPrefix : DidResolveErr { }
public DidResolveErr(E e) => this.e = e;

/// Did method is not supported.
public sealed record UnsupportedMethod : DidResolveErr { }
public static implicit operator DidResolveErr(E e) => new(e);

/// Another generic error happened during DID document resolution.
public sealed record Other(Exception E) : DidResolveErr { }
public enum E
{
/// Another generic error happened during DID document resolution.
Other,

/// Did method is not supported.
UnsupportedMethod,

/// Did had an invalid prefix.
InvalidPrefix,
}
}

/// Errors relating to the DID URL Fragment.
public abstract record DidFragmentErr : DidVerifyErr
public readonly struct DidFragmentErr : IDidVerifyErr
{
private DidFragmentErr() { }
public readonly E e;

/// No such fragment was present in the DID document.
public sealed record NoSuchFragment : DidFragmentErr { }
public DidFragmentErr(E e) => this.e = e;

/// The given fragment was ambiguous.
public sealed record AmbiguousFragment : DidFragmentErr { }
}
public static implicit operator DidFragmentErr(E e) => new(e);

/// Errors relating to the signature validity.
public abstract record DidSignatureErr : DidVerifyErr
{
private DidSignatureErr() { }
public enum E
{
/// No such fragment was present in the DID document.
AmbiguousFragment,

// TODO: Define failure modes
/// The given fragment was ambiguous.
NoSuchFragment,
}
}

/// Toplevel error type.
public abstract record DidVerifyErr
public readonly struct DidSignatureErr : IDidVerifyErr
{
internal DidVerifyErr() { } // Seals class
public readonly E e;

public DidSignatureErr(E e) => this.e = e;

public static implicit operator DidSignatureErr(E e) => new(e);

public enum E { }
}

public interface IDidVerifyErr { }

// TODO(@thebutlah): Create and implement an `IChallengeResponseAuth`
// interface. This interface should live in basis core.
public sealed class DidAuthentication
Expand Down Expand Up @@ -103,29 +111,38 @@ public Challenge MakeChallenge(Did identity)
///
/// It is the caller's responsibility to keep track of which challenges
/// should be held for which responses.
public async Task<Result<Success, DidVerifyErr>> VerifyResponse(
public async Task<Result<Empty, IDidVerifyErr>> VerifyResponse(
Response response,
Challenge challenge
)
{
var resolveResult = await ResolveDid(challenge.Identity); /*var pubkey = RetrieveKey(document, response.DidUrlFragment);*/
if (resolveResult is Result<DidDocument, DidResolveErr>.Err(var err))
var resolveResult = await ResolveDid(challenge.Identity);
if (resolveResult.IsErr)
{
return resolveResult.GetErr;
}
DidDocument document = resolveResult.GetOk;

var pubkeyResult = RetrieveKey(document, response.DidUrlFragment);
if (pubkeyResult.IsErr)
{
return new Result<Success, DidVerifyErr>.Err(err);
return pubkeyResult.GetErr;
}
var (isVerified, verifySigErr) = VerifySignature(
var pubkey = pubkeyResult.GetOk;

var sigResult = VerifySignature(
pubkey,
challenge.Nonce,
response.Signature
);
if (!isVerified)
if (sigResult.IsErr)
{
return VerifyResponseResult.Success;
return sigResult.GetErr;
}
return VerifyResponseResult.Success;
return new Empty();
}

private Result<Success, DidSignatureErr> VerifySignature(
private Result<Empty, DidSignatureErr> VerifySignature(
JsonWebKey pubkey,
Nonce nonce,
Signature signature
Expand All @@ -134,29 +151,23 @@ Signature signature
throw new NotImplementedException("todo: do the cryptography");
}

private Result<JsonWebKey, DidFragmentErr> RetrieveKey(
private static Result<JsonWebKey, DidFragmentErr> RetrieveKey(
DidDocument document,
DidUrlFragment keyId
)
{
JsonWebKey pubkeyJwk;
if (keyId.V.Equals(string.Empty))
if (document.Pubkeys.Count == 1)
{
if (document.Pubkeys.Count != 1)
{
return new Result<>.Err(new DidFragmentErr.AmbiguousFragment());
throw new DidResolveException(DidResolveErr.AmbiguousFragment);
}
pubkeyJwk = document.Pubkeys.First().Value;
return document.Pubkeys.First().Value;
}
if (!document.Pubkeys.TryGetValue(keyId, out pubkeyJwk))
if (!document.Pubkeys.TryGetValue(keyId, out JsonWebKey pubkey))
{
throw new DidResolveException(DidResolveErr.NoSuchFragment);
return new DidFragmentErr(DidFragmentErr.E.NoSuchFragment);
}
return pubkeyJwk;
return pubkey;
}

static async Task<Result<DidDocument, DidResolveErr>> ResolveDid(Did identity)
private async Task<Result<DidDocument, DidResolveErr>> ResolveDid(Did identity)
{
var segments = identity.V.Split(
separator: ":",
Expand All @@ -165,13 +176,19 @@ static async Task<Result<DidDocument, DidResolveErr>> ResolveDid(Did identity)
);
if (segments.Length != 3 || segments[0] != "did")
{
throw new DidResolveException(DidResolveErr.InvalidPrefix);
return new DidResolveErr(DidResolveErr.E.InvalidPrefix);
}
var method = segments[1] switch
DidMethodKind? methodOrNull = segments[1] switch
{
"key" => DidMethodKind.Key,
_ => throw new DidResolveException(DidResolveErr.UnsupportedMethod),
_ => null,
};
if (methodOrNull is null)
{
return new DidResolveErr(DidResolveErr.E.UnsupportedMethod);
}
var method = methodOrNull.Value;

var resolver = Resolvers[method];
return await resolver.ResolveDocument(identity);
}
Expand Down
46 changes: 37 additions & 9 deletions Contrib/Auth/Did/Result.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
#nullable enable
using System;

namespace Basis.Contrib.Auth.DecentralizedIds.Result
namespace Basis.Contrib.Auth.DecentralizedIds
{
public readonly struct Success { }

/// Analagous to rust's Result type.
public abstract record Result<T, E>
public readonly struct Result<T, E>
{
private Result() { }
private readonly bool isOk;
private readonly T? ok;
private readonly E? err;

private Result(T? ok, E? err, bool isOk)
{
this.ok = ok;
this.err = err;
this.isOk = isOk;
}

public bool IsOk => isOk;
public bool IsErr => !isOk;

public T GetOk => ok ?? throw new InvalidVariantExeption();

public E GetErr => err ?? throw new InvalidVariantExeption();

public sealed record Ok(T Ok) : Result<T, E> { }
public static Result<T, E> Ok(T v)
{
return new(v, default, true);
}

public sealed record Err(E Err) : Result<T, E> { }
public static Result<T, E> Err(E e)
{
return new(default, e, false);
}

public static implicit operator Result<T, E>(T v) => new(v, default, true);

public static implicit operator Result<T, E>(E e) => new(default, e, false);
}

public class InvalidVariantExeption : System.Exception
{
public InvalidVariantExeption()
: base("wrong result variant") { }
}
}

0 comments on commit 430f8a3

Please sign in to comment.