Freeze DodoSSH.Contracts v0.1 (M1)

The second M1 gate. This assembly, not the OpenAPI document, is the client's contract,
so PublicApiAnalyzers now tracks all 540 public members: a renamed DTO property becomes
a build error rather than a runtime deserialisation failure on someone's laptop.

Contract surface:
- EncryptedPayload carries the envelope plus the KeyGeneration and AadVersion columns
  needed to recompute AAD, since AAD is derived from the row rather than transmitted.
- Sync: push with per-operation status (Applied/Conflict/Forbidden/Invalid/Duplicate) so
  one stale item cannot block a whole offline queue; a Conflict returns the server's row
  for client-side three-way merge, because the server cannot merge ciphertext.
- Enrollment: KeyStatement whose hash becomes the OIDC nonce, so the identity provider
  signs over the public keys and this server cannot fabricate a key for a user who never
  enrolled.
- Meta and .well-known configuration: capability negotiation instead of URL versioning,
  which is what a self-hosted product needs when client and server upgrade independently.
- SyncPlaintextFields deliberately has no label or name field. ACL admin runs client-side
  where names can be decrypted, so the server never needs a searchable title.

Two design problems found by writing the tests rather than assuming:
- Hand-constructing JsonSerializerOptions and merely pointing its resolver at the context
  silently discards every source-generated setting. JsonSerializerDefaults.Web replaces
  NumberHandling.Strict with AllowReadingFromString, so "1" would be accepted where 1 is
  meant — invisible until two implementations disagree. Callers now use ResponseOptions or
  StrictRequestOptions; StrictRequestOptions is derived by copying so it cannot drift.
- StrictRequestOptions had a static-initialisation cycle: it read the generated Default
  property from the same type's initialiser and got null. Now lazy.

Requests reject unmapped members so a client typo is a 400; responses tolerate them so an
older client can read a newer server. Enums cross the wire as strings, so reordering one
cannot silently reinterpret stored data.

Also: excluded source-generator output from PublicApiAnalyzers. The JSON generator emits a
public member per serialisable type, which would have added hundreds of mechanical entries
and drowned the ones describing the actual wire contract. And disabled MA0048's
one-type-per-file rule: splitting SyncPullRequest from SyncPullResponse makes a reviewer
open two files to understand one endpoint.

Verified: 0 warnings, 95 tests pass, format clean.
This commit is contained in:
2026-07-28 13:28:02 +02:00
parent b15af836a3
commit 06d04b490b
12 changed files with 1472 additions and 0 deletions
@@ -0,0 +1,86 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DodoSSH.Contracts;
/// <summary>
/// Source-generated JSON contracts, shared by the API and the client so both sides serialise
/// identically.
/// </summary>
/// <remarks>
/// <para>
/// Source generation rather than reflection: no startup reflection cost, and it keeps the
/// assembly trim- and AOT-compatible.
/// </para>
/// <para>
/// Always use <see cref="ResponseOptions"/> or <see cref="StrictRequestOptions"/>. Constructing
/// <see cref="JsonSerializerOptions"/> by hand and merely pointing its resolver at this context
/// silently discards every setting configured here — notably
/// <see cref="JsonNumberHandling.Strict"/>, which <see cref="JsonSerializerDefaults.Web"/>
/// otherwise replaces with <see cref="JsonNumberHandling.AllowReadingFromString"/>. That failure
/// is invisible until two implementations disagree about whether <c>"1"</c> is a valid number.
/// </para>
/// </remarks>
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.Strict,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
UseStringEnumConverter = true)]
[JsonSerializable(typeof(MetaResponse))]
[JsonSerializable(typeof(DodoSshConfiguration))]
[JsonSerializable(typeof(MeResponse))]
[JsonSerializable(typeof(EnrollmentRequest))]
[JsonSerializable(typeof(EnrollmentResponse))]
[JsonSerializable(typeof(DirectoryEntry))]
[JsonSerializable(typeof(IReadOnlyList<DirectoryEntry>))]
[JsonSerializable(typeof(VaultSummary))]
[JsonSerializable(typeof(SyncPullRequest))]
[JsonSerializable(typeof(SyncPullResponse))]
[JsonSerializable(typeof(SyncPushRequest))]
[JsonSerializable(typeof(SyncPushResponse))]
[JsonSerializable(typeof(SyncChange))]
[JsonSerializable(typeof(RelayTicketRequest))]
[JsonSerializable(typeof(RelayTicketResponse))]
[JsonSerializable(typeof(RelaySessionSummary))]
[JsonSerializable(typeof(IReadOnlyList<RelaySessionSummary>))]
public sealed partial class DodoSshJsonContext : JsonSerializerContext
{
/// <summary>
/// The canonical options for writing responses and for reading them on the client.
/// </summary>
/// <remarks>
/// Unmapped members are tolerated here on purpose: an older client must still be able to
/// read a newer server's response rather than failing on a field it does not know about.
/// </remarks>
public static JsonSerializerOptions ResponseOptions => Default.Options;
/// <summary>
/// Options for deserialising inbound request bodies.
/// </summary>
/// <remarks>
/// Identical to <see cref="ResponseOptions"/> except that unmapped members are rejected, so
/// a client bug — a renamed or misspelled property — surfaces as a 400 rather than as a
/// silently missing value that later looks like data loss. Derived by copying rather than
/// constructed fresh, so it inherits every source-generated setting.
/// </remarks>
public static JsonSerializerOptions StrictRequestOptions => LazyStrictRequestOptions.Value;
/// <remarks>
/// Lazy, not a static initialiser. The generated <c>Default</c> property is a static of this
/// same class, so reading it from this type's initialiser is a cycle: the accessor runs
/// before its backing field is assigned and yields null.
/// </remarks>
private static readonly Lazy<JsonSerializerOptions> LazyStrictRequestOptions =
new(CreateStrictRequestOptions, LazyThreadSafetyMode.ExecutionAndPublication);
private static JsonSerializerOptions CreateStrictRequestOptions()
{
var options = new JsonSerializerOptions(Default.Options)
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
};
options.MakeReadOnly();
return options;
}
}
+25
View File
@@ -0,0 +1,25 @@
namespace DodoSSH.Contracts;
/// <summary>
/// An opaque ciphertext blob together with the plaintext metadata needed to recompute its AAD.
/// </summary>
/// <remarks>
/// <para>
/// The server stores and returns these verbatim. It cannot decrypt one, cannot validate the
/// contents, and cannot merge two. See docs/crypto.md.
/// </para>
/// <para>
/// <see cref="Envelope"/> is a complete DSH1 envelope and already carries its own algorithm
/// identifier and nonce, so neither is repeated here. <see cref="KeyGeneration"/> and
/// <see cref="AadVersion"/> are stored as columns because the AAD is recomputed from the row
/// rather than transmitted, and because they are what makes a lazy re-encrypt-on-write
/// migration possible later.
/// </para>
/// </remarks>
/// <param name="Envelope">The complete DSH1 envelope, base64 on the wire.</param>
/// <param name="KeyGeneration">Vault key generation this payload was encrypted under.</param>
/// <param name="AadVersion">Version of the AAD derivation rule used.</param>
public sealed record EncryptedPayload(
byte[] Envelope,
uint KeyGeneration,
byte AadVersion);
+176
View File
@@ -0,0 +1,176 @@
namespace DodoSSH.Contracts;
/// <summary>
/// Argon2id parameters as stored and transmitted.
/// </summary>
/// <remarks>
/// Stored in plaintext per wrap row. Salts are not secrets, and keeping the parameters with the
/// wrap makes raising them later a per-user, unlock-time migration rather than a breaking
/// change — an older client can still open its own wrap.
/// <para>
/// <see cref="MemoryKibibytes"/> is kibibytes, matching both the storage column and libsodium.
/// Client code should go through <c>Argon2Profile</c> rather than handling the number directly;
/// see docs/crypto.md §2 for why that unit needs guarding.
/// </para>
/// </remarks>
/// <param name="Algorithm">KDF identifier. Currently always <c>argon2id</c>.</param>
/// <param name="Salt">16-byte salt.</param>
/// <param name="MemoryKibibytes">Memory cost in KiB.</param>
/// <param name="Passes">Number of passes.</param>
/// <param name="Parallelism">Lanes. Always 1; libsodium supports no other value.</param>
public sealed record KdfParameters(
string Algorithm,
byte[] Salt,
int MemoryKibibytes,
int Passes,
int Parallelism);
/// <summary>
/// The self-describing, signed statement binding a user to their public keys.
/// </summary>
/// <remarks>
/// Its SHA-256 is used as the <c>nonce</c> of a fresh OIDC authorization, so the resulting ID
/// token is signed by the identity provider over exactly these keys. That is what stops the
/// DodoSSH server fabricating a key for a user who never enrolled. See ADR 0001.
/// </remarks>
/// <param name="Version">Statement format version.</param>
/// <param name="Issuer">OIDC issuer.</param>
/// <param name="Subject">OIDC subject.</param>
/// <param name="Email">Email at enrollment time, for display only.</param>
/// <param name="EncryptionPublicKey">X25519 public key, 32 bytes.</param>
/// <param name="SigningPublicKey">Ed25519 public key, 32 bytes.</param>
/// <param name="KeyGeneration">Generation of this key pair, starting at 1.</param>
/// <param name="CreatedAt">When the client generated the keys.</param>
/// <param name="DeviceName">Human-readable name of the enrolling device.</param>
public sealed record KeyStatement(
int Version,
string Issuer,
string Subject,
string? Email,
byte[] EncryptionPublicKey,
byte[] SigningPublicKey,
int KeyGeneration,
DateTimeOffset CreatedAt,
string DeviceName);
/// <summary>A request to enroll a user's first identity key pair.</summary>
/// <param name="Statement">The key statement.</param>
/// <param name="StatementSignature">Ed25519 self-signature over the statement.</param>
/// <param name="IdentityProviderToken">
/// An ID token whose <c>nonce</c> equals the SHA-256 of the statement. The server verifies it
/// against the provider's JWKS.
/// </param>
/// <param name="WrappedPrivateKey">
/// The user's secret bundle sealed under the passphrase-derived key. Opaque to the server.
/// </param>
/// <param name="KdfParameters">Parameters needed to re-derive the wrapping key.</param>
/// <param name="DevicePublicKey">
/// X25519 public key of this device, so the bundle can also be wrapped to the device and
/// unlocked without re-entering the passphrase.
/// </param>
/// <param name="RecoveryWrappedPrivateKey">
/// The same bundle sealed under a recovery-code-derived key.
/// </param>
/// <param name="RecoveryKdfParameters">Parameters for the recovery wrap.</param>
public sealed record EnrollmentRequest(
KeyStatement Statement,
byte[] StatementSignature,
string IdentityProviderToken,
byte[] WrappedPrivateKey,
KdfParameters KdfParameters,
byte[]? DevicePublicKey,
byte[]? RecoveryWrappedPrivateKey,
KdfParameters? RecoveryKdfParameters);
/// <summary>Result of a successful enrollment.</summary>
/// <param name="UserId">The user's identifier.</param>
/// <param name="KeyGeneration">The generation now current.</param>
/// <param name="Fingerprint">Identity fingerprint over both public keys.</param>
/// <param name="PersonalVaultId">The automatically created personal vault.</param>
/// <param name="DeviceId">The enrolled device, when a device key was supplied.</param>
/// <param name="KeyLogSequence">
/// Position of this statement in the append-only key log. Clients cache the log head and
/// include it in signed operations, which is what makes a forked view detectable.
/// </param>
public sealed record EnrollmentResponse(
Guid UserId,
int KeyGeneration,
byte[] Fingerprint,
Guid PersonalVaultId,
Guid? DeviceId,
long KeyLogSequence);
/// <summary>A public-key directory entry.</summary>
/// <remarks>
/// Before wrapping a vault key to one of these, a client must verify it: check the identity
/// provider binding, compare against its pinned fingerprint, and confirm the key log head is
/// consistent. Wrapping to an unverified key is the one mistake that undoes end-to-end
/// encryption entirely.
/// </remarks>
/// <param name="UserId">The user.</param>
/// <param name="Email">Email, for display.</param>
/// <param name="DisplayName">Display name.</param>
/// <param name="EncryptionPublicKey">X25519 public key.</param>
/// <param name="SigningPublicKey">Ed25519 public key.</param>
/// <param name="Fingerprint">Identity fingerprint.</param>
/// <param name="KeyGeneration">Generation of this key pair.</param>
/// <param name="KeyLogSequence">Key log position of the statement that introduced it.</param>
public sealed record DirectoryEntry(
Guid UserId,
string? Email,
string? DisplayName,
byte[] EncryptionPublicKey,
byte[] SigningPublicKey,
byte[] Fingerprint,
int KeyGeneration,
long KeyLogSequence);
/// <summary>The caller's own profile and unlock state.</summary>
/// <param name="UserId">The user.</param>
/// <param name="Issuer">OIDC issuer.</param>
/// <param name="Subject">OIDC subject.</param>
/// <param name="Email">Email.</param>
/// <param name="DisplayName">Display name.</param>
/// <param name="EnrollmentRequired">
/// True when no identity key exists yet, so the client must run enrollment before anything else.
/// </param>
/// <param name="KeyGeneration">Current key generation, when enrolled.</param>
/// <param name="WrappedPrivateKey">
/// The passphrase wrap of the secret bundle, for unlocking on this device.
/// </param>
/// <param name="KdfParameters">Parameters to re-derive the wrapping key.</param>
/// <param name="Vaults">Vaults the caller can reach.</param>
public sealed record MeResponse(
Guid UserId,
string Issuer,
string Subject,
string? Email,
string? DisplayName,
bool EnrollmentRequired,
int? KeyGeneration,
byte[]? WrappedPrivateKey,
KdfParameters? KdfParameters,
IReadOnlyList<VaultSummary> Vaults);
/// <summary>A vault the caller can reach, with the wrapped key needed to open it.</summary>
/// <param name="VaultId">The vault.</param>
/// <param name="Name">Display name. Plaintext, and only for vaults, not for items.</param>
/// <param name="IsPersonal">Whether this is the caller's personal vault.</param>
/// <param name="TeamId">Owning team, for a team vault.</param>
/// <param name="KeyGeneration">Current key generation.</param>
/// <param name="Permissions">The caller's effective permissions, as a flags value.</param>
/// <param name="WrappedVaultKey">
/// The vault key sealed to the caller's X25519 key. Absent when a grant is awaiting re-wrap
/// after a rekey, in which case the vault is temporarily unreadable and a member holding Share
/// must complete it.
/// </param>
/// <param name="RekeyRequired">Whether a membership change has left this vault needing a rekey.</param>
public sealed record VaultSummary(
Guid VaultId,
string Name,
bool IsPersonal,
Guid? TeamId,
uint KeyGeneration,
int Permissions,
byte[]? WrappedVaultKey,
bool RekeyRequired);
+76
View File
@@ -0,0 +1,76 @@
namespace DodoSSH.Contracts;
/// <summary>
/// Server capabilities, versions and limits.
/// </summary>
/// <remarks>
/// This replaces URL-based API versioning. In a self-hosted product the client and server
/// upgrade independently, so skew is normal rather than exceptional, and a client needs to ask
/// what this particular server supports rather than assume. See ADR 0002.
/// </remarks>
/// <param name="ServerVersion">Informational build version.</param>
/// <param name="ApiVersions">Supported API major versions.</param>
/// <param name="SyncProtocolVersion">
/// Sync semantics version. A mismatch means the client must not push.
/// </param>
/// <param name="CryptoSpecVersion">
/// DSH1 specification version the server's stored data conforms to.
/// </param>
/// <param name="Features">
/// Enabled optional features, such as <c>relay</c> or <c>teams</c>. Absence must be treated as
/// unsupported rather than as an error.
/// </param>
/// <param name="MinClientVersion">
/// Oldest client this server will serve. Clients below this must show a remediation screen
/// rather than failing obscurely.
/// </param>
/// <param name="MaxOperationsPerPush">Cap on operations in one push.</param>
/// <param name="MaxPayloadBytes">Cap on total ciphertext in one push.</param>
/// <param name="MaxItemPayloadBytes">Cap on a single item's ciphertext.</param>
public sealed record MetaResponse(
string ServerVersion,
IReadOnlyList<int> ApiVersions,
int SyncProtocolVersion,
int CryptoSpecVersion,
IReadOnlyList<string> Features,
string? MinClientVersion,
int MaxOperationsPerPush,
long MaxPayloadBytes,
long MaxItemPayloadBytes);
/// <summary>
/// Everything a client needs to begin authenticating, from one URL.
/// </summary>
/// <remarks>
/// Served anonymously at <c>/.well-known/dodossh-configuration</c>. This is the onboarding
/// story: the user types a server URL and the client discovers the rest.
/// </remarks>
/// <param name="ApiBaseUrl">Base URL for API calls.</param>
/// <param name="Oidc">Identity provider settings.</param>
/// <param name="Relay">Relay settings.</param>
public sealed record DodoSshConfiguration(
Uri ApiBaseUrl,
OidcConfiguration Oidc,
RelayConfiguration Relay);
/// <summary>Identity provider settings for a public native client.</summary>
/// <param name="Authority">Issuer URL, from which discovery and JWKS are fetched.</param>
/// <param name="ClientId">Public client identifier.</param>
/// <param name="Scopes">Scopes to request.</param>
/// <param name="LoopbackRedirectPattern">
/// Registered loopback redirect pattern. RFC 8252: a native client uses a loopback redirect on
/// an ephemeral port with the system browser, never a custom scheme and never an embedded
/// browser, so the user can see the real address bar.
/// </param>
public sealed record OidcConfiguration(
Uri Authority,
string ClientId,
IReadOnlyList<string> Scopes,
string LoopbackRedirectPattern);
/// <summary>Relay settings.</summary>
/// <param name="Enabled">Whether this server offers a relay at all.</param>
/// <param name="WebSocketUrl">Relay endpoint, when enabled.</param>
public sealed record RelayConfiguration(
bool Enabled,
Uri? WebSocketUrl);
@@ -1,5 +1,415 @@
#nullable enable
DodoSSH.Contracts.DirectoryEntry
DodoSSH.Contracts.DirectoryEntry.<Clone>$() -> DodoSSH.Contracts.DirectoryEntry!
DodoSSH.Contracts.DirectoryEntry.Deconstruct(out System.Guid UserId, out string? Email, out string? DisplayName, out byte[]! EncryptionPublicKey, out byte[]! SigningPublicKey, out byte[]! Fingerprint, out int KeyGeneration, out long KeyLogSequence) -> void
DodoSSH.Contracts.DirectoryEntry.DirectoryEntry(System.Guid UserId, string? Email, string? DisplayName, byte[]! EncryptionPublicKey, byte[]! SigningPublicKey, byte[]! Fingerprint, int KeyGeneration, long KeyLogSequence) -> void
DodoSSH.Contracts.DirectoryEntry.DisplayName.get -> string?
DodoSSH.Contracts.DirectoryEntry.DisplayName.init -> void
DodoSSH.Contracts.DirectoryEntry.Email.get -> string?
DodoSSH.Contracts.DirectoryEntry.Email.init -> void
DodoSSH.Contracts.DirectoryEntry.EncryptionPublicKey.get -> byte[]!
DodoSSH.Contracts.DirectoryEntry.EncryptionPublicKey.init -> void
DodoSSH.Contracts.DirectoryEntry.Equals(DodoSSH.Contracts.DirectoryEntry? other) -> bool
DodoSSH.Contracts.DirectoryEntry.Fingerprint.get -> byte[]!
DodoSSH.Contracts.DirectoryEntry.Fingerprint.init -> void
DodoSSH.Contracts.DirectoryEntry.KeyGeneration.get -> int
DodoSSH.Contracts.DirectoryEntry.KeyGeneration.init -> void
DodoSSH.Contracts.DirectoryEntry.KeyLogSequence.get -> long
DodoSSH.Contracts.DirectoryEntry.KeyLogSequence.init -> void
DodoSSH.Contracts.DirectoryEntry.SigningPublicKey.get -> byte[]!
DodoSSH.Contracts.DirectoryEntry.SigningPublicKey.init -> void
DodoSSH.Contracts.DirectoryEntry.UserId.get -> System.Guid
DodoSSH.Contracts.DirectoryEntry.UserId.init -> void
DodoSSH.Contracts.DodoSshConfiguration
DodoSSH.Contracts.DodoSshConfiguration.<Clone>$() -> DodoSSH.Contracts.DodoSshConfiguration!
DodoSSH.Contracts.DodoSshConfiguration.ApiBaseUrl.get -> System.Uri!
DodoSSH.Contracts.DodoSshConfiguration.ApiBaseUrl.init -> void
DodoSSH.Contracts.DodoSshConfiguration.Deconstruct(out System.Uri! ApiBaseUrl, out DodoSSH.Contracts.OidcConfiguration! Oidc, out DodoSSH.Contracts.RelayConfiguration! Relay) -> void
DodoSSH.Contracts.DodoSshConfiguration.DodoSshConfiguration(System.Uri! ApiBaseUrl, DodoSSH.Contracts.OidcConfiguration! Oidc, DodoSSH.Contracts.RelayConfiguration! Relay) -> void
DodoSSH.Contracts.DodoSshConfiguration.Equals(DodoSSH.Contracts.DodoSshConfiguration? other) -> bool
DodoSSH.Contracts.DodoSshConfiguration.Oidc.get -> DodoSSH.Contracts.OidcConfiguration!
DodoSSH.Contracts.DodoSshConfiguration.Oidc.init -> void
DodoSSH.Contracts.DodoSshConfiguration.Relay.get -> DodoSSH.Contracts.RelayConfiguration!
DodoSSH.Contracts.DodoSshConfiguration.Relay.init -> void
DodoSSH.Contracts.DodoSshJsonContext
DodoSSH.Contracts.EncryptedPayload
DodoSSH.Contracts.EncryptedPayload.<Clone>$() -> DodoSSH.Contracts.EncryptedPayload!
DodoSSH.Contracts.EncryptedPayload.AadVersion.get -> byte
DodoSSH.Contracts.EncryptedPayload.AadVersion.init -> void
DodoSSH.Contracts.EncryptedPayload.Deconstruct(out byte[]! Envelope, out uint KeyGeneration, out byte AadVersion) -> void
DodoSSH.Contracts.EncryptedPayload.EncryptedPayload(byte[]! Envelope, uint KeyGeneration, byte AadVersion) -> void
DodoSSH.Contracts.EncryptedPayload.Envelope.get -> byte[]!
DodoSSH.Contracts.EncryptedPayload.Envelope.init -> void
DodoSSH.Contracts.EncryptedPayload.Equals(DodoSSH.Contracts.EncryptedPayload? other) -> bool
DodoSSH.Contracts.EncryptedPayload.KeyGeneration.get -> uint
DodoSSH.Contracts.EncryptedPayload.KeyGeneration.init -> void
DodoSSH.Contracts.EnrollmentRequest
DodoSSH.Contracts.EnrollmentRequest.<Clone>$() -> DodoSSH.Contracts.EnrollmentRequest!
DodoSSH.Contracts.EnrollmentRequest.Deconstruct(out DodoSSH.Contracts.KeyStatement! Statement, out byte[]! StatementSignature, out string! IdentityProviderToken, out byte[]! WrappedPrivateKey, out DodoSSH.Contracts.KdfParameters! KdfParameters, out byte[]? DevicePublicKey, out byte[]? RecoveryWrappedPrivateKey, out DodoSSH.Contracts.KdfParameters? RecoveryKdfParameters) -> void
DodoSSH.Contracts.EnrollmentRequest.DevicePublicKey.get -> byte[]?
DodoSSH.Contracts.EnrollmentRequest.DevicePublicKey.init -> void
DodoSSH.Contracts.EnrollmentRequest.EnrollmentRequest(DodoSSH.Contracts.KeyStatement! Statement, byte[]! StatementSignature, string! IdentityProviderToken, byte[]! WrappedPrivateKey, DodoSSH.Contracts.KdfParameters! KdfParameters, byte[]? DevicePublicKey, byte[]? RecoveryWrappedPrivateKey, DodoSSH.Contracts.KdfParameters? RecoveryKdfParameters) -> void
DodoSSH.Contracts.EnrollmentRequest.Equals(DodoSSH.Contracts.EnrollmentRequest? other) -> bool
DodoSSH.Contracts.EnrollmentRequest.IdentityProviderToken.get -> string!
DodoSSH.Contracts.EnrollmentRequest.IdentityProviderToken.init -> void
DodoSSH.Contracts.EnrollmentRequest.KdfParameters.get -> DodoSSH.Contracts.KdfParameters!
DodoSSH.Contracts.EnrollmentRequest.KdfParameters.init -> void
DodoSSH.Contracts.EnrollmentRequest.RecoveryKdfParameters.get -> DodoSSH.Contracts.KdfParameters?
DodoSSH.Contracts.EnrollmentRequest.RecoveryKdfParameters.init -> void
DodoSSH.Contracts.EnrollmentRequest.RecoveryWrappedPrivateKey.get -> byte[]?
DodoSSH.Contracts.EnrollmentRequest.RecoveryWrappedPrivateKey.init -> void
DodoSSH.Contracts.EnrollmentRequest.Statement.get -> DodoSSH.Contracts.KeyStatement!
DodoSSH.Contracts.EnrollmentRequest.Statement.init -> void
DodoSSH.Contracts.EnrollmentRequest.StatementSignature.get -> byte[]!
DodoSSH.Contracts.EnrollmentRequest.StatementSignature.init -> void
DodoSSH.Contracts.EnrollmentRequest.WrappedPrivateKey.get -> byte[]!
DodoSSH.Contracts.EnrollmentRequest.WrappedPrivateKey.init -> void
DodoSSH.Contracts.EnrollmentResponse
DodoSSH.Contracts.EnrollmentResponse.<Clone>$() -> DodoSSH.Contracts.EnrollmentResponse!
DodoSSH.Contracts.EnrollmentResponse.Deconstruct(out System.Guid UserId, out int KeyGeneration, out byte[]! Fingerprint, out System.Guid PersonalVaultId, out System.Guid? DeviceId, out long KeyLogSequence) -> void
DodoSSH.Contracts.EnrollmentResponse.DeviceId.get -> System.Guid?
DodoSSH.Contracts.EnrollmentResponse.DeviceId.init -> void
DodoSSH.Contracts.EnrollmentResponse.EnrollmentResponse(System.Guid UserId, int KeyGeneration, byte[]! Fingerprint, System.Guid PersonalVaultId, System.Guid? DeviceId, long KeyLogSequence) -> void
DodoSSH.Contracts.EnrollmentResponse.Equals(DodoSSH.Contracts.EnrollmentResponse? other) -> bool
DodoSSH.Contracts.EnrollmentResponse.Fingerprint.get -> byte[]!
DodoSSH.Contracts.EnrollmentResponse.Fingerprint.init -> void
DodoSSH.Contracts.EnrollmentResponse.KeyGeneration.get -> int
DodoSSH.Contracts.EnrollmentResponse.KeyGeneration.init -> void
DodoSSH.Contracts.EnrollmentResponse.KeyLogSequence.get -> long
DodoSSH.Contracts.EnrollmentResponse.KeyLogSequence.init -> void
DodoSSH.Contracts.EnrollmentResponse.PersonalVaultId.get -> System.Guid
DodoSSH.Contracts.EnrollmentResponse.PersonalVaultId.init -> void
DodoSSH.Contracts.EnrollmentResponse.UserId.get -> System.Guid
DodoSSH.Contracts.EnrollmentResponse.UserId.init -> void
DodoSSH.Contracts.KdfParameters
DodoSSH.Contracts.KdfParameters.<Clone>$() -> DodoSSH.Contracts.KdfParameters!
DodoSSH.Contracts.KdfParameters.Algorithm.get -> string!
DodoSSH.Contracts.KdfParameters.Algorithm.init -> void
DodoSSH.Contracts.KdfParameters.Deconstruct(out string! Algorithm, out byte[]! Salt, out int MemoryKibibytes, out int Passes, out int Parallelism) -> void
DodoSSH.Contracts.KdfParameters.Equals(DodoSSH.Contracts.KdfParameters? other) -> bool
DodoSSH.Contracts.KdfParameters.KdfParameters(string! Algorithm, byte[]! Salt, int MemoryKibibytes, int Passes, int Parallelism) -> void
DodoSSH.Contracts.KdfParameters.MemoryKibibytes.get -> int
DodoSSH.Contracts.KdfParameters.MemoryKibibytes.init -> void
DodoSSH.Contracts.KdfParameters.Parallelism.get -> int
DodoSSH.Contracts.KdfParameters.Parallelism.init -> void
DodoSSH.Contracts.KdfParameters.Passes.get -> int
DodoSSH.Contracts.KdfParameters.Passes.init -> void
DodoSSH.Contracts.KdfParameters.Salt.get -> byte[]!
DodoSSH.Contracts.KdfParameters.Salt.init -> void
DodoSSH.Contracts.KeyStatement
DodoSSH.Contracts.KeyStatement.<Clone>$() -> DodoSSH.Contracts.KeyStatement!
DodoSSH.Contracts.KeyStatement.CreatedAt.get -> System.DateTimeOffset
DodoSSH.Contracts.KeyStatement.CreatedAt.init -> void
DodoSSH.Contracts.KeyStatement.Deconstruct(out int Version, out string! Issuer, out string! Subject, out string? Email, out byte[]! EncryptionPublicKey, out byte[]! SigningPublicKey, out int KeyGeneration, out System.DateTimeOffset CreatedAt, out string! DeviceName) -> void
DodoSSH.Contracts.KeyStatement.DeviceName.get -> string!
DodoSSH.Contracts.KeyStatement.DeviceName.init -> void
DodoSSH.Contracts.KeyStatement.Email.get -> string?
DodoSSH.Contracts.KeyStatement.Email.init -> void
DodoSSH.Contracts.KeyStatement.EncryptionPublicKey.get -> byte[]!
DodoSSH.Contracts.KeyStatement.EncryptionPublicKey.init -> void
DodoSSH.Contracts.KeyStatement.Equals(DodoSSH.Contracts.KeyStatement? other) -> bool
DodoSSH.Contracts.KeyStatement.Issuer.get -> string!
DodoSSH.Contracts.KeyStatement.Issuer.init -> void
DodoSSH.Contracts.KeyStatement.KeyGeneration.get -> int
DodoSSH.Contracts.KeyStatement.KeyGeneration.init -> void
DodoSSH.Contracts.KeyStatement.KeyStatement(int Version, string! Issuer, string! Subject, string? Email, byte[]! EncryptionPublicKey, byte[]! SigningPublicKey, int KeyGeneration, System.DateTimeOffset CreatedAt, string! DeviceName) -> void
DodoSSH.Contracts.KeyStatement.SigningPublicKey.get -> byte[]!
DodoSSH.Contracts.KeyStatement.SigningPublicKey.init -> void
DodoSSH.Contracts.KeyStatement.Subject.get -> string!
DodoSSH.Contracts.KeyStatement.Subject.init -> void
DodoSSH.Contracts.KeyStatement.Version.get -> int
DodoSSH.Contracts.KeyStatement.Version.init -> void
DodoSSH.Contracts.MeResponse
DodoSSH.Contracts.MeResponse.<Clone>$() -> DodoSSH.Contracts.MeResponse!
DodoSSH.Contracts.MeResponse.Deconstruct(out System.Guid UserId, out string! Issuer, out string! Subject, out string? Email, out string? DisplayName, out bool EnrollmentRequired, out int? KeyGeneration, out byte[]? WrappedPrivateKey, out DodoSSH.Contracts.KdfParameters? KdfParameters, out System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.VaultSummary!>! Vaults) -> void
DodoSSH.Contracts.MeResponse.DisplayName.get -> string?
DodoSSH.Contracts.MeResponse.DisplayName.init -> void
DodoSSH.Contracts.MeResponse.Email.get -> string?
DodoSSH.Contracts.MeResponse.Email.init -> void
DodoSSH.Contracts.MeResponse.EnrollmentRequired.get -> bool
DodoSSH.Contracts.MeResponse.EnrollmentRequired.init -> void
DodoSSH.Contracts.MeResponse.Equals(DodoSSH.Contracts.MeResponse? other) -> bool
DodoSSH.Contracts.MeResponse.Issuer.get -> string!
DodoSSH.Contracts.MeResponse.Issuer.init -> void
DodoSSH.Contracts.MeResponse.KdfParameters.get -> DodoSSH.Contracts.KdfParameters?
DodoSSH.Contracts.MeResponse.KdfParameters.init -> void
DodoSSH.Contracts.MeResponse.KeyGeneration.get -> int?
DodoSSH.Contracts.MeResponse.KeyGeneration.init -> void
DodoSSH.Contracts.MeResponse.MeResponse(System.Guid UserId, string! Issuer, string! Subject, string? Email, string? DisplayName, bool EnrollmentRequired, int? KeyGeneration, byte[]? WrappedPrivateKey, DodoSSH.Contracts.KdfParameters? KdfParameters, System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.VaultSummary!>! Vaults) -> void
DodoSSH.Contracts.MeResponse.Subject.get -> string!
DodoSSH.Contracts.MeResponse.Subject.init -> void
DodoSSH.Contracts.MeResponse.UserId.get -> System.Guid
DodoSSH.Contracts.MeResponse.UserId.init -> void
DodoSSH.Contracts.MeResponse.Vaults.get -> System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.VaultSummary!>!
DodoSSH.Contracts.MeResponse.Vaults.init -> void
DodoSSH.Contracts.MeResponse.WrappedPrivateKey.get -> byte[]?
DodoSSH.Contracts.MeResponse.WrappedPrivateKey.init -> void
DodoSSH.Contracts.MetaResponse
DodoSSH.Contracts.MetaResponse.<Clone>$() -> DodoSSH.Contracts.MetaResponse!
DodoSSH.Contracts.MetaResponse.ApiVersions.get -> System.Collections.Generic.IReadOnlyList<int>!
DodoSSH.Contracts.MetaResponse.ApiVersions.init -> void
DodoSSH.Contracts.MetaResponse.CryptoSpecVersion.get -> int
DodoSSH.Contracts.MetaResponse.CryptoSpecVersion.init -> void
DodoSSH.Contracts.MetaResponse.Deconstruct(out string! ServerVersion, out System.Collections.Generic.IReadOnlyList<int>! ApiVersions, out int SyncProtocolVersion, out int CryptoSpecVersion, out System.Collections.Generic.IReadOnlyList<string!>! Features, out string? MinClientVersion, out int MaxOperationsPerPush, out long MaxPayloadBytes, out long MaxItemPayloadBytes) -> void
DodoSSH.Contracts.MetaResponse.Equals(DodoSSH.Contracts.MetaResponse? other) -> bool
DodoSSH.Contracts.MetaResponse.Features.get -> System.Collections.Generic.IReadOnlyList<string!>!
DodoSSH.Contracts.MetaResponse.Features.init -> void
DodoSSH.Contracts.MetaResponse.MaxItemPayloadBytes.get -> long
DodoSSH.Contracts.MetaResponse.MaxItemPayloadBytes.init -> void
DodoSSH.Contracts.MetaResponse.MaxOperationsPerPush.get -> int
DodoSSH.Contracts.MetaResponse.MaxOperationsPerPush.init -> void
DodoSSH.Contracts.MetaResponse.MaxPayloadBytes.get -> long
DodoSSH.Contracts.MetaResponse.MaxPayloadBytes.init -> void
DodoSSH.Contracts.MetaResponse.MetaResponse(string! ServerVersion, System.Collections.Generic.IReadOnlyList<int>! ApiVersions, int SyncProtocolVersion, int CryptoSpecVersion, System.Collections.Generic.IReadOnlyList<string!>! Features, string? MinClientVersion, int MaxOperationsPerPush, long MaxPayloadBytes, long MaxItemPayloadBytes) -> void
DodoSSH.Contracts.MetaResponse.MinClientVersion.get -> string?
DodoSSH.Contracts.MetaResponse.MinClientVersion.init -> void
DodoSSH.Contracts.MetaResponse.ServerVersion.get -> string!
DodoSSH.Contracts.MetaResponse.ServerVersion.init -> void
DodoSSH.Contracts.MetaResponse.SyncProtocolVersion.get -> int
DodoSSH.Contracts.MetaResponse.SyncProtocolVersion.init -> void
DodoSSH.Contracts.OidcConfiguration
DodoSSH.Contracts.OidcConfiguration.<Clone>$() -> DodoSSH.Contracts.OidcConfiguration!
DodoSSH.Contracts.OidcConfiguration.Authority.get -> System.Uri!
DodoSSH.Contracts.OidcConfiguration.Authority.init -> void
DodoSSH.Contracts.OidcConfiguration.ClientId.get -> string!
DodoSSH.Contracts.OidcConfiguration.ClientId.init -> void
DodoSSH.Contracts.OidcConfiguration.Deconstruct(out System.Uri! Authority, out string! ClientId, out System.Collections.Generic.IReadOnlyList<string!>! Scopes, out string! LoopbackRedirectPattern) -> void
DodoSSH.Contracts.OidcConfiguration.Equals(DodoSSH.Contracts.OidcConfiguration? other) -> bool
DodoSSH.Contracts.OidcConfiguration.LoopbackRedirectPattern.get -> string!
DodoSSH.Contracts.OidcConfiguration.LoopbackRedirectPattern.init -> void
DodoSSH.Contracts.OidcConfiguration.OidcConfiguration(System.Uri! Authority, string! ClientId, System.Collections.Generic.IReadOnlyList<string!>! Scopes, string! LoopbackRedirectPattern) -> void
DodoSSH.Contracts.OidcConfiguration.Scopes.get -> System.Collections.Generic.IReadOnlyList<string!>!
DodoSSH.Contracts.OidcConfiguration.Scopes.init -> void
DodoSSH.Contracts.ProblemCodes
DodoSSH.Contracts.RelayConfiguration
DodoSSH.Contracts.RelayConfiguration.<Clone>$() -> DodoSSH.Contracts.RelayConfiguration!
DodoSSH.Contracts.RelayConfiguration.Deconstruct(out bool Enabled, out System.Uri? WebSocketUrl) -> void
DodoSSH.Contracts.RelayConfiguration.Enabled.get -> bool
DodoSSH.Contracts.RelayConfiguration.Enabled.init -> void
DodoSSH.Contracts.RelayConfiguration.Equals(DodoSSH.Contracts.RelayConfiguration? other) -> bool
DodoSSH.Contracts.RelayConfiguration.RelayConfiguration(bool Enabled, System.Uri? WebSocketUrl) -> void
DodoSSH.Contracts.RelayConfiguration.WebSocketUrl.get -> System.Uri?
DodoSSH.Contracts.RelayConfiguration.WebSocketUrl.init -> void
DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.ClientClosed = 1 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.DurationLimit = 4 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.Error = 7 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.IdleTimeout = 3 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.ServerShutdown = 5 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.TargetClosed = 2 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.TargetUnreachable = 6 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionCloseReason.Unspecified = 0 -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionSummary
DodoSSH.Contracts.RelaySessionSummary.<Clone>$() -> DodoSSH.Contracts.RelaySessionSummary!
DodoSSH.Contracts.RelaySessionSummary.BytesClientToTarget.get -> long
DodoSSH.Contracts.RelaySessionSummary.BytesClientToTarget.init -> void
DodoSSH.Contracts.RelaySessionSummary.BytesTargetToClient.get -> long
DodoSSH.Contracts.RelaySessionSummary.BytesTargetToClient.init -> void
DodoSSH.Contracts.RelaySessionSummary.CloseReason.get -> DodoSSH.Contracts.RelaySessionCloseReason
DodoSSH.Contracts.RelaySessionSummary.CloseReason.init -> void
DodoSSH.Contracts.RelaySessionSummary.Deconstruct(out System.Guid SessionId, out System.Guid HostId, out string! TargetHost, out int TargetPort, out System.DateTimeOffset StartedAt, out System.DateTimeOffset? EndedAt, out long BytesClientToTarget, out long BytesTargetToClient, out DodoSSH.Contracts.RelaySessionCloseReason CloseReason) -> void
DodoSSH.Contracts.RelaySessionSummary.EndedAt.get -> System.DateTimeOffset?
DodoSSH.Contracts.RelaySessionSummary.EndedAt.init -> void
DodoSSH.Contracts.RelaySessionSummary.Equals(DodoSSH.Contracts.RelaySessionSummary? other) -> bool
DodoSSH.Contracts.RelaySessionSummary.HostId.get -> System.Guid
DodoSSH.Contracts.RelaySessionSummary.HostId.init -> void
DodoSSH.Contracts.RelaySessionSummary.RelaySessionSummary(System.Guid SessionId, System.Guid HostId, string! TargetHost, int TargetPort, System.DateTimeOffset StartedAt, System.DateTimeOffset? EndedAt, long BytesClientToTarget, long BytesTargetToClient, DodoSSH.Contracts.RelaySessionCloseReason CloseReason) -> void
DodoSSH.Contracts.RelaySessionSummary.SessionId.get -> System.Guid
DodoSSH.Contracts.RelaySessionSummary.SessionId.init -> void
DodoSSH.Contracts.RelaySessionSummary.StartedAt.get -> System.DateTimeOffset
DodoSSH.Contracts.RelaySessionSummary.StartedAt.init -> void
DodoSSH.Contracts.RelaySessionSummary.TargetHost.get -> string!
DodoSSH.Contracts.RelaySessionSummary.TargetHost.init -> void
DodoSSH.Contracts.RelaySessionSummary.TargetPort.get -> int
DodoSSH.Contracts.RelaySessionSummary.TargetPort.init -> void
DodoSSH.Contracts.RelayTicketRequest
DodoSSH.Contracts.RelayTicketRequest.<Clone>$() -> DodoSSH.Contracts.RelayTicketRequest!
DodoSSH.Contracts.RelayTicketRequest.Deconstruct(out System.Guid HostId, out System.Guid? PortForwardId) -> void
DodoSSH.Contracts.RelayTicketRequest.Equals(DodoSSH.Contracts.RelayTicketRequest? other) -> bool
DodoSSH.Contracts.RelayTicketRequest.HostId.get -> System.Guid
DodoSSH.Contracts.RelayTicketRequest.HostId.init -> void
DodoSSH.Contracts.RelayTicketRequest.PortForwardId.get -> System.Guid?
DodoSSH.Contracts.RelayTicketRequest.PortForwardId.init -> void
DodoSSH.Contracts.RelayTicketRequest.RelayTicketRequest(System.Guid HostId, System.Guid? PortForwardId) -> void
DodoSSH.Contracts.RelayTicketResponse
DodoSSH.Contracts.RelayTicketResponse.<Clone>$() -> DodoSSH.Contracts.RelayTicketResponse!
DodoSSH.Contracts.RelayTicketResponse.Deconstruct(out string! Ticket, out System.Uri! WebSocketUrl, out string! SubProtocol, out System.Guid SessionId, out System.DateTimeOffset ExpiresAt) -> void
DodoSSH.Contracts.RelayTicketResponse.Equals(DodoSSH.Contracts.RelayTicketResponse? other) -> bool
DodoSSH.Contracts.RelayTicketResponse.ExpiresAt.get -> System.DateTimeOffset
DodoSSH.Contracts.RelayTicketResponse.ExpiresAt.init -> void
DodoSSH.Contracts.RelayTicketResponse.RelayTicketResponse(string! Ticket, System.Uri! WebSocketUrl, string! SubProtocol, System.Guid SessionId, System.DateTimeOffset ExpiresAt) -> void
DodoSSH.Contracts.RelayTicketResponse.SessionId.get -> System.Guid
DodoSSH.Contracts.RelayTicketResponse.SessionId.init -> void
DodoSSH.Contracts.RelayTicketResponse.SubProtocol.get -> string!
DodoSSH.Contracts.RelayTicketResponse.SubProtocol.init -> void
DodoSSH.Contracts.RelayTicketResponse.Ticket.get -> string!
DodoSSH.Contracts.RelayTicketResponse.Ticket.init -> void
DodoSSH.Contracts.RelayTicketResponse.WebSocketUrl.get -> System.Uri!
DodoSSH.Contracts.RelayTicketResponse.WebSocketUrl.init -> void
DodoSSH.Contracts.SyncChange
DodoSSH.Contracts.SyncChange.<Clone>$() -> DodoSSH.Contracts.SyncChange!
DodoSSH.Contracts.SyncChange.ChangeSequence.get -> long
DodoSSH.Contracts.SyncChange.ChangeSequence.init -> void
DodoSSH.Contracts.SyncChange.Deconstruct(out DodoSSH.Contracts.SyncEntityType EntityType, out System.Guid EntityId, out DodoSSH.Contracts.SyncOperation Operation, out int Version, out long ChangeSequence, out DodoSSH.Contracts.EncryptedPayload? Payload, out DodoSSH.Contracts.SyncPlaintextFields? PlaintextFields, out System.DateTimeOffset UpdatedAt) -> void
DodoSSH.Contracts.SyncChange.EntityId.get -> System.Guid
DodoSSH.Contracts.SyncChange.EntityId.init -> void
DodoSSH.Contracts.SyncChange.EntityType.get -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncChange.EntityType.init -> void
DodoSSH.Contracts.SyncChange.Equals(DodoSSH.Contracts.SyncChange? other) -> bool
DodoSSH.Contracts.SyncChange.Operation.get -> DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncChange.Operation.init -> void
DodoSSH.Contracts.SyncChange.Payload.get -> DodoSSH.Contracts.EncryptedPayload?
DodoSSH.Contracts.SyncChange.Payload.init -> void
DodoSSH.Contracts.SyncChange.PlaintextFields.get -> DodoSSH.Contracts.SyncPlaintextFields?
DodoSSH.Contracts.SyncChange.PlaintextFields.init -> void
DodoSSH.Contracts.SyncChange.SyncChange(DodoSSH.Contracts.SyncEntityType EntityType, System.Guid EntityId, DodoSSH.Contracts.SyncOperation Operation, int Version, long ChangeSequence, DodoSSH.Contracts.EncryptedPayload? Payload, DodoSSH.Contracts.SyncPlaintextFields? PlaintextFields, System.DateTimeOffset UpdatedAt) -> void
DodoSSH.Contracts.SyncChange.UpdatedAt.get -> System.DateTimeOffset
DodoSSH.Contracts.SyncChange.UpdatedAt.init -> void
DodoSSH.Contracts.SyncChange.Version.get -> int
DodoSSH.Contracts.SyncChange.Version.init -> void
DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.Credential = 2 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.Host = 1 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.HostCredential = 7 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.HostGroup = 4 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.HostTag = 6 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.KnownHostKey = 10 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.PortForward = 9 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.Snippet = 8 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.SshKey = 3 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.Tag = 5 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncEntityType.Unspecified = 0 -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncOperation.Delete = 2 -> DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncOperation.Unspecified = 0 -> DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncOperation.Upsert = 1 -> DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Applied = 1 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Conflict = 2 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Duplicate = 5 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Forbidden = 3 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Invalid = 4 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncOperationStatus.Unspecified = 0 -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncPlaintextFields
DodoSSH.Contracts.SyncPlaintextFields.<Clone>$() -> DodoSSH.Contracts.SyncPlaintextFields!
DodoSSH.Contracts.SyncPlaintextFields.Deconstruct(out bool RelayEnabled, out string? Hostname, out int? Port, out System.Guid? GroupId, out System.Guid? ParentId, out System.Guid? RelatedId, out int? Kind, out string? PublicKeyFingerprint) -> void
DodoSSH.Contracts.SyncPlaintextFields.Equals(DodoSSH.Contracts.SyncPlaintextFields? other) -> bool
DodoSSH.Contracts.SyncPlaintextFields.GroupId.get -> System.Guid?
DodoSSH.Contracts.SyncPlaintextFields.GroupId.init -> void
DodoSSH.Contracts.SyncPlaintextFields.Hostname.get -> string?
DodoSSH.Contracts.SyncPlaintextFields.Hostname.init -> void
DodoSSH.Contracts.SyncPlaintextFields.Kind.get -> int?
DodoSSH.Contracts.SyncPlaintextFields.Kind.init -> void
DodoSSH.Contracts.SyncPlaintextFields.ParentId.get -> System.Guid?
DodoSSH.Contracts.SyncPlaintextFields.ParentId.init -> void
DodoSSH.Contracts.SyncPlaintextFields.Port.get -> int?
DodoSSH.Contracts.SyncPlaintextFields.Port.init -> void
DodoSSH.Contracts.SyncPlaintextFields.PublicKeyFingerprint.get -> string?
DodoSSH.Contracts.SyncPlaintextFields.PublicKeyFingerprint.init -> void
DodoSSH.Contracts.SyncPlaintextFields.RelatedId.get -> System.Guid?
DodoSSH.Contracts.SyncPlaintextFields.RelatedId.init -> void
DodoSSH.Contracts.SyncPlaintextFields.RelayEnabled.get -> bool
DodoSSH.Contracts.SyncPlaintextFields.RelayEnabled.init -> void
DodoSSH.Contracts.SyncPlaintextFields.SyncPlaintextFields(bool RelayEnabled = false, string? Hostname = null, int? Port = null, System.Guid? GroupId = null, System.Guid? ParentId = null, System.Guid? RelatedId = null, int? Kind = null, string? PublicKeyFingerprint = null) -> void
DodoSSH.Contracts.SyncPullRequest
DodoSSH.Contracts.SyncPullRequest.<Clone>$() -> DodoSSH.Contracts.SyncPullRequest!
DodoSSH.Contracts.SyncPullRequest.Cursor.get -> string?
DodoSSH.Contracts.SyncPullRequest.Cursor.init -> void
DodoSSH.Contracts.SyncPullRequest.Deconstruct(out string? Cursor, out int? Limit, out System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncEntityType>? EntityTypes) -> void
DodoSSH.Contracts.SyncPullRequest.EntityTypes.get -> System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncEntityType>?
DodoSSH.Contracts.SyncPullRequest.EntityTypes.init -> void
DodoSSH.Contracts.SyncPullRequest.Equals(DodoSSH.Contracts.SyncPullRequest? other) -> bool
DodoSSH.Contracts.SyncPullRequest.Limit.get -> int?
DodoSSH.Contracts.SyncPullRequest.Limit.init -> void
DodoSSH.Contracts.SyncPullRequest.SyncPullRequest(string? Cursor, int? Limit, System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncEntityType>? EntityTypes) -> void
DodoSSH.Contracts.SyncPullResponse
DodoSSH.Contracts.SyncPullResponse.<Clone>$() -> DodoSSH.Contracts.SyncPullResponse!
DodoSSH.Contracts.SyncPullResponse.Changes.get -> System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncChange!>!
DodoSSH.Contracts.SyncPullResponse.Changes.init -> void
DodoSSH.Contracts.SyncPullResponse.CurrentKeyGeneration.get -> uint
DodoSSH.Contracts.SyncPullResponse.CurrentKeyGeneration.init -> void
DodoSSH.Contracts.SyncPullResponse.Deconstruct(out System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncChange!>! Changes, out string! NextCursor, out bool HasMore, out System.DateTimeOffset ServerTime, out uint CurrentKeyGeneration) -> void
DodoSSH.Contracts.SyncPullResponse.Equals(DodoSSH.Contracts.SyncPullResponse? other) -> bool
DodoSSH.Contracts.SyncPullResponse.HasMore.get -> bool
DodoSSH.Contracts.SyncPullResponse.HasMore.init -> void
DodoSSH.Contracts.SyncPullResponse.NextCursor.get -> string!
DodoSSH.Contracts.SyncPullResponse.NextCursor.init -> void
DodoSSH.Contracts.SyncPullResponse.ServerTime.get -> System.DateTimeOffset
DodoSSH.Contracts.SyncPullResponse.ServerTime.init -> void
DodoSSH.Contracts.SyncPullResponse.SyncPullResponse(System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncChange!>! Changes, string! NextCursor, bool HasMore, System.DateTimeOffset ServerTime, uint CurrentKeyGeneration) -> void
DodoSSH.Contracts.SyncPushOperation
DodoSSH.Contracts.SyncPushOperation.<Clone>$() -> DodoSSH.Contracts.SyncPushOperation!
DodoSSH.Contracts.SyncPushOperation.Deconstruct(out System.Guid OperationId, out DodoSSH.Contracts.SyncEntityType EntityType, out System.Guid EntityId, out DodoSSH.Contracts.SyncOperation Operation, out int? ExpectedVersion, out DodoSSH.Contracts.EncryptedPayload? Payload, out DodoSSH.Contracts.SyncPlaintextFields? PlaintextFields) -> void
DodoSSH.Contracts.SyncPushOperation.EntityId.get -> System.Guid
DodoSSH.Contracts.SyncPushOperation.EntityId.init -> void
DodoSSH.Contracts.SyncPushOperation.EntityType.get -> DodoSSH.Contracts.SyncEntityType
DodoSSH.Contracts.SyncPushOperation.EntityType.init -> void
DodoSSH.Contracts.SyncPushOperation.Equals(DodoSSH.Contracts.SyncPushOperation? other) -> bool
DodoSSH.Contracts.SyncPushOperation.ExpectedVersion.get -> int?
DodoSSH.Contracts.SyncPushOperation.ExpectedVersion.init -> void
DodoSSH.Contracts.SyncPushOperation.Operation.get -> DodoSSH.Contracts.SyncOperation
DodoSSH.Contracts.SyncPushOperation.Operation.init -> void
DodoSSH.Contracts.SyncPushOperation.OperationId.get -> System.Guid
DodoSSH.Contracts.SyncPushOperation.OperationId.init -> void
DodoSSH.Contracts.SyncPushOperation.Payload.get -> DodoSSH.Contracts.EncryptedPayload?
DodoSSH.Contracts.SyncPushOperation.Payload.init -> void
DodoSSH.Contracts.SyncPushOperation.PlaintextFields.get -> DodoSSH.Contracts.SyncPlaintextFields?
DodoSSH.Contracts.SyncPushOperation.PlaintextFields.init -> void
DodoSSH.Contracts.SyncPushOperation.SyncPushOperation(System.Guid OperationId, DodoSSH.Contracts.SyncEntityType EntityType, System.Guid EntityId, DodoSSH.Contracts.SyncOperation Operation, int? ExpectedVersion, DodoSSH.Contracts.EncryptedPayload? Payload, DodoSSH.Contracts.SyncPlaintextFields? PlaintextFields) -> void
DodoSSH.Contracts.SyncPushRequest
DodoSSH.Contracts.SyncPushRequest.<Clone>$() -> DodoSSH.Contracts.SyncPushRequest!
DodoSSH.Contracts.SyncPushRequest.Deconstruct(out System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushOperation!>! Operations) -> void
DodoSSH.Contracts.SyncPushRequest.Equals(DodoSSH.Contracts.SyncPushRequest? other) -> bool
DodoSSH.Contracts.SyncPushRequest.Operations.get -> System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushOperation!>!
DodoSSH.Contracts.SyncPushRequest.Operations.init -> void
DodoSSH.Contracts.SyncPushRequest.SyncPushRequest(System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushOperation!>! Operations) -> void
DodoSSH.Contracts.SyncPushResponse
DodoSSH.Contracts.SyncPushResponse.<Clone>$() -> DodoSSH.Contracts.SyncPushResponse!
DodoSSH.Contracts.SyncPushResponse.Cursor.get -> string!
DodoSSH.Contracts.SyncPushResponse.Cursor.init -> void
DodoSSH.Contracts.SyncPushResponse.Deconstruct(out System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushResult!>! Results, out string! Cursor) -> void
DodoSSH.Contracts.SyncPushResponse.Equals(DodoSSH.Contracts.SyncPushResponse? other) -> bool
DodoSSH.Contracts.SyncPushResponse.Results.get -> System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushResult!>!
DodoSSH.Contracts.SyncPushResponse.Results.init -> void
DodoSSH.Contracts.SyncPushResponse.SyncPushResponse(System.Collections.Generic.IReadOnlyList<DodoSSH.Contracts.SyncPushResult!>! Results, string! Cursor) -> void
DodoSSH.Contracts.SyncPushResult
DodoSSH.Contracts.SyncPushResult.<Clone>$() -> DodoSSH.Contracts.SyncPushResult!
DodoSSH.Contracts.SyncPushResult.ChangeSequence.get -> long?
DodoSSH.Contracts.SyncPushResult.ChangeSequence.init -> void
DodoSSH.Contracts.SyncPushResult.Deconstruct(out System.Guid OperationId, out DodoSSH.Contracts.SyncOperationStatus Status, out int? Version, out long? ChangeSequence, out DodoSSH.Contracts.SyncChange? ServerEntity, out string? Detail) -> void
DodoSSH.Contracts.SyncPushResult.Detail.get -> string?
DodoSSH.Contracts.SyncPushResult.Detail.init -> void
DodoSSH.Contracts.SyncPushResult.Equals(DodoSSH.Contracts.SyncPushResult? other) -> bool
DodoSSH.Contracts.SyncPushResult.OperationId.get -> System.Guid
DodoSSH.Contracts.SyncPushResult.OperationId.init -> void
DodoSSH.Contracts.SyncPushResult.ServerEntity.get -> DodoSSH.Contracts.SyncChange?
DodoSSH.Contracts.SyncPushResult.ServerEntity.init -> void
DodoSSH.Contracts.SyncPushResult.Status.get -> DodoSSH.Contracts.SyncOperationStatus
DodoSSH.Contracts.SyncPushResult.Status.init -> void
DodoSSH.Contracts.SyncPushResult.SyncPushResult(System.Guid OperationId, DodoSSH.Contracts.SyncOperationStatus Status, int? Version, long? ChangeSequence, DodoSSH.Contracts.SyncChange? ServerEntity, string? Detail) -> void
DodoSSH.Contracts.SyncPushResult.Version.get -> int?
DodoSSH.Contracts.SyncPushResult.Version.init -> void
DodoSSH.Contracts.VaultSummary
DodoSSH.Contracts.VaultSummary.<Clone>$() -> DodoSSH.Contracts.VaultSummary!
DodoSSH.Contracts.VaultSummary.Deconstruct(out System.Guid VaultId, out string! Name, out bool IsPersonal, out System.Guid? TeamId, out uint KeyGeneration, out int Permissions, out byte[]? WrappedVaultKey, out bool RekeyRequired) -> void
DodoSSH.Contracts.VaultSummary.Equals(DodoSSH.Contracts.VaultSummary? other) -> bool
DodoSSH.Contracts.VaultSummary.IsPersonal.get -> bool
DodoSSH.Contracts.VaultSummary.IsPersonal.init -> void
DodoSSH.Contracts.VaultSummary.KeyGeneration.get -> uint
DodoSSH.Contracts.VaultSummary.KeyGeneration.init -> void
DodoSSH.Contracts.VaultSummary.Name.get -> string!
DodoSSH.Contracts.VaultSummary.Name.init -> void
DodoSSH.Contracts.VaultSummary.Permissions.get -> int
DodoSSH.Contracts.VaultSummary.Permissions.init -> void
DodoSSH.Contracts.VaultSummary.RekeyRequired.get -> bool
DodoSSH.Contracts.VaultSummary.RekeyRequired.init -> void
DodoSSH.Contracts.VaultSummary.TeamId.get -> System.Guid?
DodoSSH.Contracts.VaultSummary.TeamId.init -> void
DodoSSH.Contracts.VaultSummary.VaultId.get -> System.Guid
DodoSSH.Contracts.VaultSummary.VaultId.init -> void
DodoSSH.Contracts.VaultSummary.VaultSummary(System.Guid VaultId, string! Name, bool IsPersonal, System.Guid? TeamId, uint KeyGeneration, int Permissions, byte[]? WrappedVaultKey, bool RekeyRequired) -> void
DodoSSH.Contracts.VaultSummary.WrappedVaultKey.get -> byte[]?
DodoSSH.Contracts.VaultSummary.WrappedVaultKey.init -> void
const DodoSSH.Contracts.ProblemCodes.AlreadyEnrolled = "already-enrolled" -> string!
const DodoSSH.Contracts.ProblemCodes.ClientTooOld = "client-too-old" -> string!
const DodoSSH.Contracts.ProblemCodes.EnrollmentRequired = "enrollment-required" -> string!
@@ -12,3 +422,120 @@ const DodoSSH.Contracts.ProblemCodes.RelayTargetRejected = "relay-target-rejecte
const DodoSSH.Contracts.ProblemCodes.RelayTicketInvalid = "relay-ticket-invalid" -> string!
const DodoSSH.Contracts.ProblemCodes.TypeBaseUri = "https://dodossh.dev/problems/" -> string!
const DodoSSH.Contracts.ProblemCodes.VaultConflict = "vault-conflict" -> string!
override DodoSSH.Contracts.DirectoryEntry.Equals(object? obj) -> bool
override DodoSSH.Contracts.DirectoryEntry.GetHashCode() -> int
override DodoSSH.Contracts.DirectoryEntry.ToString() -> string!
override DodoSSH.Contracts.DodoSshConfiguration.Equals(object? obj) -> bool
override DodoSSH.Contracts.DodoSshConfiguration.GetHashCode() -> int
override DodoSSH.Contracts.DodoSshConfiguration.ToString() -> string!
override DodoSSH.Contracts.EncryptedPayload.Equals(object? obj) -> bool
override DodoSSH.Contracts.EncryptedPayload.GetHashCode() -> int
override DodoSSH.Contracts.EncryptedPayload.ToString() -> string!
override DodoSSH.Contracts.EnrollmentRequest.Equals(object? obj) -> bool
override DodoSSH.Contracts.EnrollmentRequest.GetHashCode() -> int
override DodoSSH.Contracts.EnrollmentRequest.ToString() -> string!
override DodoSSH.Contracts.EnrollmentResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.EnrollmentResponse.GetHashCode() -> int
override DodoSSH.Contracts.EnrollmentResponse.ToString() -> string!
override DodoSSH.Contracts.KdfParameters.Equals(object? obj) -> bool
override DodoSSH.Contracts.KdfParameters.GetHashCode() -> int
override DodoSSH.Contracts.KdfParameters.ToString() -> string!
override DodoSSH.Contracts.KeyStatement.Equals(object? obj) -> bool
override DodoSSH.Contracts.KeyStatement.GetHashCode() -> int
override DodoSSH.Contracts.KeyStatement.ToString() -> string!
override DodoSSH.Contracts.MeResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.MeResponse.GetHashCode() -> int
override DodoSSH.Contracts.MeResponse.ToString() -> string!
override DodoSSH.Contracts.MetaResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.MetaResponse.GetHashCode() -> int
override DodoSSH.Contracts.MetaResponse.ToString() -> string!
override DodoSSH.Contracts.OidcConfiguration.Equals(object? obj) -> bool
override DodoSSH.Contracts.OidcConfiguration.GetHashCode() -> int
override DodoSSH.Contracts.OidcConfiguration.ToString() -> string!
override DodoSSH.Contracts.RelayConfiguration.Equals(object? obj) -> bool
override DodoSSH.Contracts.RelayConfiguration.GetHashCode() -> int
override DodoSSH.Contracts.RelayConfiguration.ToString() -> string!
override DodoSSH.Contracts.RelaySessionSummary.Equals(object? obj) -> bool
override DodoSSH.Contracts.RelaySessionSummary.GetHashCode() -> int
override DodoSSH.Contracts.RelaySessionSummary.ToString() -> string!
override DodoSSH.Contracts.RelayTicketRequest.Equals(object? obj) -> bool
override DodoSSH.Contracts.RelayTicketRequest.GetHashCode() -> int
override DodoSSH.Contracts.RelayTicketRequest.ToString() -> string!
override DodoSSH.Contracts.RelayTicketResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.RelayTicketResponse.GetHashCode() -> int
override DodoSSH.Contracts.RelayTicketResponse.ToString() -> string!
override DodoSSH.Contracts.SyncChange.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncChange.GetHashCode() -> int
override DodoSSH.Contracts.SyncChange.ToString() -> string!
override DodoSSH.Contracts.SyncPlaintextFields.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPlaintextFields.GetHashCode() -> int
override DodoSSH.Contracts.SyncPlaintextFields.ToString() -> string!
override DodoSSH.Contracts.SyncPullRequest.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPullRequest.GetHashCode() -> int
override DodoSSH.Contracts.SyncPullRequest.ToString() -> string!
override DodoSSH.Contracts.SyncPullResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPullResponse.GetHashCode() -> int
override DodoSSH.Contracts.SyncPullResponse.ToString() -> string!
override DodoSSH.Contracts.SyncPushOperation.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPushOperation.GetHashCode() -> int
override DodoSSH.Contracts.SyncPushOperation.ToString() -> string!
override DodoSSH.Contracts.SyncPushRequest.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPushRequest.GetHashCode() -> int
override DodoSSH.Contracts.SyncPushRequest.ToString() -> string!
override DodoSSH.Contracts.SyncPushResponse.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPushResponse.GetHashCode() -> int
override DodoSSH.Contracts.SyncPushResponse.ToString() -> string!
override DodoSSH.Contracts.SyncPushResult.Equals(object? obj) -> bool
override DodoSSH.Contracts.SyncPushResult.GetHashCode() -> int
override DodoSSH.Contracts.SyncPushResult.ToString() -> string!
override DodoSSH.Contracts.VaultSummary.Equals(object? obj) -> bool
override DodoSSH.Contracts.VaultSummary.GetHashCode() -> int
override DodoSSH.Contracts.VaultSummary.ToString() -> string!
static DodoSSH.Contracts.DirectoryEntry.operator !=(DodoSSH.Contracts.DirectoryEntry? left, DodoSSH.Contracts.DirectoryEntry? right) -> bool
static DodoSSH.Contracts.DirectoryEntry.operator ==(DodoSSH.Contracts.DirectoryEntry? left, DodoSSH.Contracts.DirectoryEntry? right) -> bool
static DodoSSH.Contracts.DodoSshConfiguration.operator !=(DodoSSH.Contracts.DodoSshConfiguration? left, DodoSSH.Contracts.DodoSshConfiguration? right) -> bool
static DodoSSH.Contracts.DodoSshConfiguration.operator ==(DodoSSH.Contracts.DodoSshConfiguration? left, DodoSSH.Contracts.DodoSshConfiguration? right) -> bool
static DodoSSH.Contracts.DodoSshJsonContext.ResponseOptions.get -> System.Text.Json.JsonSerializerOptions!
static DodoSSH.Contracts.DodoSshJsonContext.StrictRequestOptions.get -> System.Text.Json.JsonSerializerOptions!
static DodoSSH.Contracts.EncryptedPayload.operator !=(DodoSSH.Contracts.EncryptedPayload? left, DodoSSH.Contracts.EncryptedPayload? right) -> bool
static DodoSSH.Contracts.EncryptedPayload.operator ==(DodoSSH.Contracts.EncryptedPayload? left, DodoSSH.Contracts.EncryptedPayload? right) -> bool
static DodoSSH.Contracts.EnrollmentRequest.operator !=(DodoSSH.Contracts.EnrollmentRequest? left, DodoSSH.Contracts.EnrollmentRequest? right) -> bool
static DodoSSH.Contracts.EnrollmentRequest.operator ==(DodoSSH.Contracts.EnrollmentRequest? left, DodoSSH.Contracts.EnrollmentRequest? right) -> bool
static DodoSSH.Contracts.EnrollmentResponse.operator !=(DodoSSH.Contracts.EnrollmentResponse? left, DodoSSH.Contracts.EnrollmentResponse? right) -> bool
static DodoSSH.Contracts.EnrollmentResponse.operator ==(DodoSSH.Contracts.EnrollmentResponse? left, DodoSSH.Contracts.EnrollmentResponse? right) -> bool
static DodoSSH.Contracts.KdfParameters.operator !=(DodoSSH.Contracts.KdfParameters? left, DodoSSH.Contracts.KdfParameters? right) -> bool
static DodoSSH.Contracts.KdfParameters.operator ==(DodoSSH.Contracts.KdfParameters? left, DodoSSH.Contracts.KdfParameters? right) -> bool
static DodoSSH.Contracts.KeyStatement.operator !=(DodoSSH.Contracts.KeyStatement? left, DodoSSH.Contracts.KeyStatement? right) -> bool
static DodoSSH.Contracts.KeyStatement.operator ==(DodoSSH.Contracts.KeyStatement? left, DodoSSH.Contracts.KeyStatement? right) -> bool
static DodoSSH.Contracts.MeResponse.operator !=(DodoSSH.Contracts.MeResponse? left, DodoSSH.Contracts.MeResponse? right) -> bool
static DodoSSH.Contracts.MeResponse.operator ==(DodoSSH.Contracts.MeResponse? left, DodoSSH.Contracts.MeResponse? right) -> bool
static DodoSSH.Contracts.MetaResponse.operator !=(DodoSSH.Contracts.MetaResponse? left, DodoSSH.Contracts.MetaResponse? right) -> bool
static DodoSSH.Contracts.MetaResponse.operator ==(DodoSSH.Contracts.MetaResponse? left, DodoSSH.Contracts.MetaResponse? right) -> bool
static DodoSSH.Contracts.OidcConfiguration.operator !=(DodoSSH.Contracts.OidcConfiguration? left, DodoSSH.Contracts.OidcConfiguration? right) -> bool
static DodoSSH.Contracts.OidcConfiguration.operator ==(DodoSSH.Contracts.OidcConfiguration? left, DodoSSH.Contracts.OidcConfiguration? right) -> bool
static DodoSSH.Contracts.RelayConfiguration.operator !=(DodoSSH.Contracts.RelayConfiguration? left, DodoSSH.Contracts.RelayConfiguration? right) -> bool
static DodoSSH.Contracts.RelayConfiguration.operator ==(DodoSSH.Contracts.RelayConfiguration? left, DodoSSH.Contracts.RelayConfiguration? right) -> bool
static DodoSSH.Contracts.RelaySessionSummary.operator !=(DodoSSH.Contracts.RelaySessionSummary? left, DodoSSH.Contracts.RelaySessionSummary? right) -> bool
static DodoSSH.Contracts.RelaySessionSummary.operator ==(DodoSSH.Contracts.RelaySessionSummary? left, DodoSSH.Contracts.RelaySessionSummary? right) -> bool
static DodoSSH.Contracts.RelayTicketRequest.operator !=(DodoSSH.Contracts.RelayTicketRequest? left, DodoSSH.Contracts.RelayTicketRequest? right) -> bool
static DodoSSH.Contracts.RelayTicketRequest.operator ==(DodoSSH.Contracts.RelayTicketRequest? left, DodoSSH.Contracts.RelayTicketRequest? right) -> bool
static DodoSSH.Contracts.RelayTicketResponse.operator !=(DodoSSH.Contracts.RelayTicketResponse? left, DodoSSH.Contracts.RelayTicketResponse? right) -> bool
static DodoSSH.Contracts.RelayTicketResponse.operator ==(DodoSSH.Contracts.RelayTicketResponse? left, DodoSSH.Contracts.RelayTicketResponse? right) -> bool
static DodoSSH.Contracts.SyncChange.operator !=(DodoSSH.Contracts.SyncChange? left, DodoSSH.Contracts.SyncChange? right) -> bool
static DodoSSH.Contracts.SyncChange.operator ==(DodoSSH.Contracts.SyncChange? left, DodoSSH.Contracts.SyncChange? right) -> bool
static DodoSSH.Contracts.SyncPlaintextFields.operator !=(DodoSSH.Contracts.SyncPlaintextFields? left, DodoSSH.Contracts.SyncPlaintextFields? right) -> bool
static DodoSSH.Contracts.SyncPlaintextFields.operator ==(DodoSSH.Contracts.SyncPlaintextFields? left, DodoSSH.Contracts.SyncPlaintextFields? right) -> bool
static DodoSSH.Contracts.SyncPullRequest.operator !=(DodoSSH.Contracts.SyncPullRequest? left, DodoSSH.Contracts.SyncPullRequest? right) -> bool
static DodoSSH.Contracts.SyncPullRequest.operator ==(DodoSSH.Contracts.SyncPullRequest? left, DodoSSH.Contracts.SyncPullRequest? right) -> bool
static DodoSSH.Contracts.SyncPullResponse.operator !=(DodoSSH.Contracts.SyncPullResponse? left, DodoSSH.Contracts.SyncPullResponse? right) -> bool
static DodoSSH.Contracts.SyncPullResponse.operator ==(DodoSSH.Contracts.SyncPullResponse? left, DodoSSH.Contracts.SyncPullResponse? right) -> bool
static DodoSSH.Contracts.SyncPushOperation.operator !=(DodoSSH.Contracts.SyncPushOperation? left, DodoSSH.Contracts.SyncPushOperation? right) -> bool
static DodoSSH.Contracts.SyncPushOperation.operator ==(DodoSSH.Contracts.SyncPushOperation? left, DodoSSH.Contracts.SyncPushOperation? right) -> bool
static DodoSSH.Contracts.SyncPushRequest.operator !=(DodoSSH.Contracts.SyncPushRequest? left, DodoSSH.Contracts.SyncPushRequest? right) -> bool
static DodoSSH.Contracts.SyncPushRequest.operator ==(DodoSSH.Contracts.SyncPushRequest? left, DodoSSH.Contracts.SyncPushRequest? right) -> bool
static DodoSSH.Contracts.SyncPushResponse.operator !=(DodoSSH.Contracts.SyncPushResponse? left, DodoSSH.Contracts.SyncPushResponse? right) -> bool
static DodoSSH.Contracts.SyncPushResponse.operator ==(DodoSSH.Contracts.SyncPushResponse? left, DodoSSH.Contracts.SyncPushResponse? right) -> bool
static DodoSSH.Contracts.SyncPushResult.operator !=(DodoSSH.Contracts.SyncPushResult? left, DodoSSH.Contracts.SyncPushResult? right) -> bool
static DodoSSH.Contracts.SyncPushResult.operator ==(DodoSSH.Contracts.SyncPushResult? left, DodoSSH.Contracts.SyncPushResult? right) -> bool
static DodoSSH.Contracts.VaultSummary.operator !=(DodoSSH.Contracts.VaultSummary? left, DodoSSH.Contracts.VaultSummary? right) -> bool
static DodoSSH.Contracts.VaultSummary.operator ==(DodoSSH.Contracts.VaultSummary? left, DodoSSH.Contracts.VaultSummary? right) -> bool
+99
View File
@@ -0,0 +1,99 @@
namespace DodoSSH.Contracts;
/// <summary>
/// A request for permission to relay to a host.
/// </summary>
/// <remarks>
/// There is deliberately no address field. The server resolves the target from
/// <see cref="HostId"/>, which must belong to a vault the caller holds Connect on and must have
/// relay enabled. Accepting a client-supplied address would turn the relay into an
/// authenticated open TCP proxy into the operator's network; see ADR 0004.
/// </remarks>
/// <param name="HostId">The host to reach.</param>
/// <param name="PortForwardId">
/// A configured port forward to use instead of the host's own SSH port. Resolved server-side the
/// same way.
/// </param>
public sealed record RelayTicketRequest(
Guid HostId,
Guid? PortForwardId);
/// <summary>
/// A short-lived, single-use ticket authorising exactly one relay connection.
/// </summary>
/// <remarks>
/// The ticket grants no API access at all, which is why the WebSocket endpoint can accept it
/// alone. It is also the extraction seam: a standalone relay process needs only the data
/// protection key ring and the replay-guard table, no authorization code.
/// </remarks>
/// <param name="Ticket">
/// Opaque token. Sent as the <c>ticket.&lt;token&gt;</c> element of the
/// <c>Sec-WebSocket-Protocol</c> header, because constrained WebSocket clients cannot set
/// arbitrary headers.
/// </param>
/// <param name="WebSocketUrl">Absolute URL of the relay endpoint.</param>
/// <param name="SubProtocol">Required WebSocket subprotocol.</param>
/// <param name="SessionId">Correlates the ticket with the resulting session record.</param>
/// <param name="ExpiresAt">Expiry, at most 30 seconds out.</param>
public sealed record RelayTicketResponse(
string Ticket,
Uri WebSocketUrl,
string SubProtocol,
Guid SessionId,
DateTimeOffset ExpiresAt);
/// <summary>Why a relay session ended.</summary>
public enum RelaySessionCloseReason
{
/// <summary>Not a legal value.</summary>
Unspecified = 0,
/// <summary>The client closed the connection.</summary>
ClientClosed = 1,
/// <summary>The target closed the connection.</summary>
TargetClosed = 2,
/// <summary>No traffic within the idle timeout.</summary>
IdleTimeout = 3,
/// <summary>The maximum session duration was reached.</summary>
DurationLimit = 4,
/// <summary>The server is shutting down and drained the session.</summary>
ServerShutdown = 5,
/// <summary>The connection to the target failed.</summary>
TargetUnreachable = 6,
/// <summary>An error ended the session.</summary>
Error = 7,
}
/// <summary>
/// An audit record of one relay session.
/// </summary>
/// <remarks>
/// Byte counts and duration are recorded; content never is. The relay forwards SSH ciphertext,
/// so session recording is impossible in this mode by construction — the compliance strength
/// and the limitation are the same fact.
/// </remarks>
/// <param name="SessionId">The session.</param>
/// <param name="HostId">Host that was reached.</param>
/// <param name="TargetHost">Resolved target hostname.</param>
/// <param name="TargetPort">Resolved target port.</param>
/// <param name="StartedAt">When the session opened.</param>
/// <param name="EndedAt">When it closed, or null while still open.</param>
/// <param name="BytesClientToTarget">Bytes forwarded from client to target.</param>
/// <param name="BytesTargetToClient">Bytes forwarded from target to client.</param>
/// <param name="CloseReason">Why it ended.</param>
public sealed record RelaySessionSummary(
Guid SessionId,
Guid HostId,
string TargetHost,
int TargetPort,
DateTimeOffset StartedAt,
DateTimeOffset? EndedAt,
long BytesClientToTarget,
long BytesTargetToClient,
RelaySessionCloseReason CloseReason);
+148
View File
@@ -0,0 +1,148 @@
namespace DodoSSH.Contracts;
/// <summary>
/// Plaintext columns that the server needs in order to function, supplied alongside a payload.
/// </summary>
/// <remarks>
/// <para>
/// Kept deliberately minimal. There is no plaintext label or name: ACL administration happens
/// in the client, which can decrypt, so the server never needs a searchable title.
/// </para>
/// <para>
/// <see cref="Hostname"/> and <see cref="Port"/> may only be set when <see cref="RelayEnabled"/>
/// is true, and the database enforces that with a CHECK constraint. The relay must resolve its
/// target server-side or it becomes an authenticated open TCP proxy into the operator's own
/// network; see ADR 0004. Everything else about a host — username, notes, jump chain, options —
/// stays inside the encrypted payload.
/// </para>
/// </remarks>
/// <param name="RelayEnabled">Whether this host may be dialled through the server relay.</param>
/// <param name="Hostname">Target hostname. Permitted only when relay is enabled.</param>
/// <param name="Port">Target port. Permitted only when relay is enabled.</param>
/// <param name="GroupId">Owning group, for tree placement.</param>
/// <param name="ParentId">Parent entity, for associations and nested groups.</param>
/// <param name="RelatedId">The second side of an association row.</param>
/// <param name="Kind">Discriminator for entity types that have one, such as credential kind.</param>
/// <param name="PublicKeyFingerprint">
/// Fingerprint of a public key. Deliberately plaintext: public keys are not secret, and this
/// enables "which hosts trust this key" without weakening the threat model.
/// </param>
public sealed record SyncPlaintextFields(
bool RelayEnabled = false,
string? Hostname = null,
int? Port = null,
Guid? GroupId = null,
Guid? ParentId = null,
Guid? RelatedId = null,
int? Kind = null,
string? PublicKeyFingerprint = null);
/// <summary>
/// One change a client wants to apply.
/// </summary>
/// <param name="OperationId">
/// Client-generated idempotency key for this single operation. A retried push with the same id
/// returns <see cref="SyncOperationStatus.Duplicate"/> rather than applying twice.
/// </param>
/// <param name="EntityType">Kind of item.</param>
/// <param name="EntityId">
/// Identifier, generated by the client with UUIDv7 so items can be created offline.
/// </param>
/// <param name="Operation">Upsert or delete.</param>
/// <param name="ExpectedVersion">
/// The version the client believes the server holds; <see langword="null"/> means create. A
/// mismatch yields <see cref="SyncOperationStatus.Conflict"/> — never last-writer-wins.
/// </param>
/// <param name="Payload">Ciphertext. Required for an upsert, omitted for a delete.</param>
/// <param name="PlaintextFields">Plaintext columns the server needs.</param>
public sealed record SyncPushOperation(
Guid OperationId,
SyncEntityType EntityType,
Guid EntityId,
SyncOperation Operation,
int? ExpectedVersion,
EncryptedPayload? Payload,
SyncPlaintextFields? PlaintextFields);
/// <summary>A batch of changes to apply to one vault.</summary>
/// <param name="Operations">
/// The batch. Capped by the server; see <see cref="MetaResponse.MaxOperationsPerPush"/>.
/// </param>
public sealed record SyncPushRequest(IReadOnlyList<SyncPushOperation> Operations);
/// <summary>Outcome of one operation in a push.</summary>
/// <param name="OperationId">Echoes the request's operation id.</param>
/// <param name="Status">What happened.</param>
/// <param name="Version">The stored version after a successful apply.</param>
/// <param name="ChangeSequence">The change-log sequence assigned, for cursor comparison.</param>
/// <param name="ServerEntity">
/// The server's current state, present only on <see cref="SyncOperationStatus.Conflict"/> so
/// the client can perform a three-way merge and re-push.
/// </param>
/// <param name="Detail">Human-readable explanation for an invalid operation. Never a secret.</param>
public sealed record SyncPushResult(
Guid OperationId,
SyncOperationStatus Status,
int? Version,
long? ChangeSequence,
SyncChange? ServerEntity,
string? Detail);
/// <summary>Per-operation outcomes for a push.</summary>
/// <param name="Results">One entry per submitted operation, in request order.</param>
/// <param name="Cursor">
/// A cursor positioned after every change this push produced, so the client can continue
/// pulling without re-reading its own writes.
/// </param>
public sealed record SyncPushResponse(
IReadOnlyList<SyncPushResult> Results,
string Cursor);
/// <summary>A request for changes since a cursor.</summary>
/// <param name="Cursor">
/// An opaque, integrity-tagged cursor from a previous response, or <see langword="null"/> to
/// start from the beginning. Clients must never construct or modify one.
/// </param>
/// <param name="Limit">Maximum changes to return. The server clamps this.</param>
/// <param name="EntityTypes">Optional filter. Empty or null means all types.</param>
public sealed record SyncPullRequest(
string? Cursor,
int? Limit,
IReadOnlyList<SyncEntityType>? EntityTypes);
/// <summary>One change as returned by a pull.</summary>
/// <param name="EntityType">Kind of item.</param>
/// <param name="EntityId">Identifier.</param>
/// <param name="Operation">Upsert or delete. A delete carries no payload.</param>
/// <param name="Version">Version after the change.</param>
/// <param name="ChangeSequence">Position in the vault's change log.</param>
/// <param name="Payload">Ciphertext, absent for a delete.</param>
/// <param name="PlaintextFields">Plaintext columns, absent for a delete.</param>
/// <param name="UpdatedAt">When the change was recorded.</param>
public sealed record SyncChange(
SyncEntityType EntityType,
Guid EntityId,
SyncOperation Operation,
int Version,
long ChangeSequence,
EncryptedPayload? Payload,
SyncPlaintextFields? PlaintextFields,
DateTimeOffset UpdatedAt);
/// <summary>A page of changes.</summary>
/// <param name="Changes">Changes in ascending change-sequence order.</param>
/// <param name="NextCursor">Cursor to pass to the following pull.</param>
/// <param name="HasMore">Whether more changes are immediately available.</param>
/// <param name="ServerTime">
/// The server's clock, so a client can detect its own skew rather than mis-ordering local edits.
/// </param>
/// <param name="CurrentKeyGeneration">
/// The vault's current key generation. A client seeing a generation ahead of its own knows a
/// rekey happened and that it must fetch new grants.
/// </param>
public sealed record SyncPullResponse(
IReadOnlyList<SyncChange> Changes,
string NextCursor,
bool HasMore,
DateTimeOffset ServerTime,
uint CurrentKeyGeneration);
+45
View File
@@ -0,0 +1,45 @@
namespace DodoSSH.Contracts;
/// <summary>
/// The kind of vault item a sync change refers to.
/// </summary>
/// <remarks>
/// Values are persisted in the change log and are part of the wire contract. Append only;
/// never renumber. These mirror <c>CryptoSpec.AadResourceType</c> for the item types, but are
/// a separate enum because only syncable items appear here.
/// </remarks>
public enum SyncEntityType
{
/// <summary>Not a legal value.</summary>
Unspecified = 0,
/// <summary>An SSH host.</summary>
Host = 1,
/// <summary>A credential.</summary>
Credential = 2,
/// <summary>An SSH key pair.</summary>
SshKey = 3,
/// <summary>A host group.</summary>
HostGroup = 4,
/// <summary>A tag.</summary>
Tag = 5,
/// <summary>A host-to-tag association.</summary>
HostTag = 6,
/// <summary>A host-to-credential association.</summary>
HostCredential = 7,
/// <summary>A command snippet.</summary>
Snippet = 8,
/// <summary>A port forward.</summary>
PortForward = 9,
/// <summary>A known SSH host key.</summary>
KnownHostKey = 10,
}
+21
View File
@@ -0,0 +1,21 @@
namespace DodoSSH.Contracts;
/// <summary>
/// What a sync change did to an entity.
/// </summary>
/// <remarks>
/// There is no hard delete. A delete is a revisioned tombstone, because a client that has been
/// offline must be able to learn that an item went away; a row that simply vanished would be
/// indistinguishable from one the client had never seen.
/// </remarks>
public enum SyncOperation
{
/// <summary>Not a legal value.</summary>
Unspecified = 0,
/// <summary>The entity was created or modified.</summary>
Upsert = 1,
/// <summary>The entity was soft-deleted, leaving a tombstone.</summary>
Delete = 2,
}
@@ -0,0 +1,37 @@
namespace DodoSSH.Contracts;
/// <summary>
/// Outcome of a single operation within a sync push.
/// </summary>
/// <remarks>
/// A push returns HTTP 200 even when some operations fail, with one of these per operation.
/// Conflicting operations are skipped rather than aborting the batch, so one stale item cannot
/// block every other change a client has queued while offline.
/// </remarks>
public enum SyncOperationStatus
{
/// <summary>Not a legal value.</summary>
Unspecified = 0,
/// <summary>Applied. The response carries the new version and change sequence.</summary>
Applied = 1,
/// <summary>
/// The expected version did not match. The response carries the server's current entity so
/// the client can merge and re-push; the server never merges, because it cannot read the
/// payload.
/// </summary>
Conflict = 2,
/// <summary>The caller lacks permission for this entity.</summary>
Forbidden = 3,
/// <summary>Structurally invalid: unknown entity type, malformed envelope, missing field.</summary>
Invalid = 4,
/// <summary>
/// This operation id was already applied. Retries are therefore exactly-once at operation
/// granularity rather than merely at batch granularity.
/// </summary>
Duplicate = 5,
}