Codebelt

Codebelt.SharedKernel

Constrained value objects for shared identifiers, timestamps, durations, and access keys.

.NET 10.0 / .NET 9.0 MIT v0.5.8 4,171 downloads

Overview

Codebelt.SharedKernel packages immutable value objects for cross-cutting concepts that tend to appear in more than one bounded context, including correlation identifiers, UTC timestamps, clock skew, time-to-live windows, secrets, and access keys. The types turn those concepts into explicit domain primitives instead of repeating string, DateTime, and TimeSpan validation throughout the codebase.

The package is most useful when several services or application layers must agree on the same validity rules for identifiers and time-based credentials. It stays focused on reusable primitives rather than broader domain modeling concerns.

Key APIs

Token encapsulates an immutable string value with configurable guards for minimum length, maximum length, embedded whitespace, and repeated-character frequency. It is the base type behind the package's string-based security and tracing primitives, and its setup callback lets consumers tighten or relax those constraints for other token-like concepts.

Secret specializes Token for sensitive string payloads and accepts either a Guid or a string. It preserves the raw value for serialization and exposes ToByteArray(...) when downstream code needs a byte representation.

CorrelationId specializes Token for distributed request tracing and fixes the repeated-character rule to require more entropy than the base Token default. The constructors and implicit conversions accept both Guid and string identifiers.

CoordinatedUniversalTime wraps a UTC DateTime and rejects non-UTC input at construction. FromString(...), Now(), and the implicit conversions make it practical to normalize timestamps before they become part of domain state or access-control logic.

ClockSkew wraps a non-negative TimeSpan with an upper bound of four hours. FromMinutes(...) and FromSeconds(...) are the package's shorthand for expressing tolerated time drift around authentication and validity checks.

TimeToLive wraps non-negative durations used for expiry windows and offers factory methods such as FromMinutes(...), FromHours(...), FromDays(...), FromMonths(...), and FromYears(...). Because it inherits comparable value object behavior, consumers can compare lifetimes without dropping back to raw TimeSpan values.

AccessKey combines a Secret, a validity window, and an allowed clock skew into one immutable value object for API-key style credentials. Issue(...) creates a key from a secret and optional TimeToLive, while the primary constructor accepts an AccessKeyOptions callback when you need explicit boundaries.

AccessKeyOptions defines the defaults and invariants behind AccessKey creation. It starts with an always-valid range and a 30 second tolerance, then throws if the configured values leave the object in an invalid state.

AccessKeyExtensions.IsValid(...) evaluates whether an access key is currently usable against a supplied or current UTC timestamp. The method includes the configured tolerance in the expiry comparison, which is the package's built-in clock-skew rule.

Basic usage

using System;
using Codebelt.Extensions.Xunit;
using Codebelt.SharedKernel;
using Codebelt.SharedKernel.Security;
using Xunit;

namespace MyProject.Tests;

public sealed class AccessKeyValidationTests : Test
{
    public AccessKeyValidationTests(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void AccessKey_ShouldHonorClockSkewWhenCheckingExpiry()
    {
        var accessKey = new AccessKey(new Secret("4f6c9480dfed47b8a99d6a54c1a8e324f1c7b2e6"), o =>
        {
            o.ValidFrom = CoordinatedUniversalTime.FromString("2026-05-30T10:00:00Z");
            o.Expires = CoordinatedUniversalTime.FromString("2026-05-30T10:15:00Z");
            o.DesiredTolerance = ClockSkew.FromSeconds(30);
        });

        var probeTime = CoordinatedUniversalTime.FromString("2026-05-30T10:15:20Z");
        var isValid = accessKey.IsValid(accessKey, probeTime);

        TestOutput.WriteLine($"Valid from: {accessKey.ValidFrom:O}");
        TestOutput.WriteLine($"Expires: {accessKey.Expires:O}");
        TestOutput.WriteLine($"Probe time: {probeTime}");

        Assert.True(isValid);
        Assert.Equal(TimeSpan.FromSeconds(30), accessKey.DesiredTolerance);
    }
}

Use this pattern when a credential must remain valid across small clock differences between the issuer and verifier. It matters because the access key carries its own validity window and tolerance instead of scattering timestamp and drift checks across authentication code.

Installation

dotnet add package Codebelt.SharedKernel

Usage guidance

Adopt this package when your solution needs the same validation rules for shared identifiers, UTC timestamps, expiry windows, or API-key style credentials in more than one place. If a value object is specific to a single domain model and does not need the token, time, or access-key semantics defined here, keep that type local to the consuming domain instead of forcing it into the shared kernel.