mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-03-20 09:01:05 +08:00
# Description of Changes This exposes JWT claims for csharp modules, similar to how they are exposed to rust modules in https://github.com/clockworklabs/SpacetimeDB/pull/3288. This adds the new types `AuthCtx` and `JwtClaims`, and adds an `AuthCtx` to the `ReducerContext`. `AuthCtx` represents the credentials associated with the request, and `JwtClaims` represents a jwt token. One difference from the rust version is that I didn't create helpers to build an `AuthCtx` from a jwt payload. The reason is that we would need to be able to compute the identity from the payload claims, which requires a blake3 hash implementation. The first two c# libraries I found had issues at runtime ([Blake3](https://www.nuget.org/packages/Blake3) is wrapping a rust implementation, and [HashifyNet](https://github.com/Deskasoft/HashifyNET/tree/main/HashifyNet/Algorithms/Blake3) seems to be broken by our trimming because it uses reflection heavily). I can look into taking the implementation from `HashifyNet`, since it is MIT licensed, but I don't think we need to block merging on that. # API and ABI breaking changes This adds the new types `AuthCtx` and `JwtClaims`, and adds an `AuthCtx` to the `ReducerContext`. This also adds a csharp wrapper for the get_jwt ABI function added in https://github.com/clockworklabs/SpacetimeDB/pull/3288. # Expected complexity level and risk 2. # Testing This has a very minimal unit test of JwtClaims. I manually tested using this locally with the csharp quickstart, and I was able to print jwt tokens inside the module.
89 lines
2.5 KiB
C#
89 lines
2.5 KiB
C#
namespace SpacetimeDB;
|
|
|
|
using System;
|
|
|
|
public sealed class AuthCtx
|
|
{
|
|
private readonly bool _isInternal;
|
|
private readonly Lazy<JwtClaims?> _jwtLazy;
|
|
|
|
private AuthCtx(bool isInternal, Func<JwtClaims?> jwtFactory)
|
|
{
|
|
_isInternal = isInternal;
|
|
_jwtLazy = new Lazy<JwtClaims?>(() => jwtFactory?.Invoke());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an AuthCtx for an internal call, with no JWT.
|
|
/// </summary>
|
|
private static AuthCtx Internal()
|
|
{
|
|
return new AuthCtx(isInternal: true, jwtFactory: () => null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an AuthCtx by looking up the credentials for a connection id in system tables.
|
|
///
|
|
/// Ideally this would not be part of the public API.
|
|
/// This should only be called inside of a reducer.
|
|
/// </summary>
|
|
public static AuthCtx BuildFromSystemTables(ConnectionId? connectionId, Identity identity)
|
|
{
|
|
if (connectionId == null)
|
|
{
|
|
return Internal();
|
|
}
|
|
return FromConnectionId(connectionId.Value, identity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an AuthCtx that reads JWT for a given connection ID.
|
|
/// </summary>
|
|
private static AuthCtx FromConnectionId(ConnectionId connectionId, Identity identity)
|
|
{
|
|
return new AuthCtx(
|
|
isInternal: false,
|
|
jwtFactory: () =>
|
|
{
|
|
var result = SpacetimeDB.Internal.FFI.get_jwt(ref connectionId, out var source);
|
|
SpacetimeDB.Internal.FFI.CheckedStatus.Marshaller.ConvertToManaged(result);
|
|
var bytes = SpacetimeDB.Internal.Module.Consume(source);
|
|
if (bytes == null || bytes.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
var jwt = System.Text.Encoding.UTF8.GetString(bytes);
|
|
return jwt != null ? new JwtClaims(jwt, identity) : null;
|
|
}
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if this reducer was spawned from inside the database.
|
|
/// </summary>
|
|
public bool IsInternal => _isInternal;
|
|
|
|
/// <summary>
|
|
/// Check if there is a JWT present.
|
|
/// If IsInternal is true, this will be false.
|
|
/// </summary>
|
|
public bool HasJwt
|
|
{
|
|
get
|
|
{
|
|
if (_isInternal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// At this point we do load the bytes.
|
|
return _jwtLazy.Value != null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load and get the JwtClaims.
|
|
/// </summary>
|
|
public JwtClaims? Jwt => _jwtLazy.Value;
|
|
}
|