Public Access
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:
@@ -118,6 +118,11 @@ dotnet_diagnostic.CA1711.severity = none
|
||||
# a non-issue in practice and it heavily constrains domain naming.
|
||||
dotnet_diagnostic.CA1724.severity = none
|
||||
|
||||
# MA0048 requires one type per file. Good for large types, actively worse for small DTO
|
||||
# clusters: splitting SyncPullRequest from SyncPullResponse means a reviewer opens two files
|
||||
# to understand one endpoint's contract. The BCL groups related types the same way.
|
||||
dotnet_diagnostic.MA0048.severity = none
|
||||
|
||||
# We use file-scoped namespaces and modern C#; these fire on deliberate style choices.
|
||||
dotnet_diagnostic.CA1812.severity = none # internal types instantiated by DI
|
||||
dotnet_diagnostic.CA1849.severity = warning # sync call in async method
|
||||
@@ -132,3 +137,15 @@ dotnet_diagnostic.CA1034.severity = none
|
||||
generated_code = true
|
||||
dotnet_analyzer_diagnostic.severity = none
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
[*.{g,g.i,generated,designer}.cs]
|
||||
# Source-generator output. In particular the System.Text.Json generator emits a public
|
||||
# JsonTypeInfo member per serialisable type, which PublicApiAnalyzers would otherwise demand
|
||||
# be tracked in PublicAPI.txt — hundreds of entries derived mechanically from the
|
||||
# [JsonSerializable] list, drowning the entries that describe the actual wire contract.
|
||||
generated_code = true
|
||||
dotnet_analyzer_diagnostic.severity = none
|
||||
dotnet_diagnostic.RS0016.severity = none
|
||||
dotnet_diagnostic.RS0017.severity = none
|
||||
dotnet_diagnostic.RS0041.severity = none
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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.<token></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);
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System.Text.Json;
|
||||
using DodoSSH.Contracts;
|
||||
|
||||
namespace DodoSSH.Contracts.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Wire-format behaviour of the shared contracts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These assert the properties the client depends on and that a compile-time reference cannot
|
||||
/// guarantee: camelCase naming, enums as strings, base64 for binary, and strict rejection of
|
||||
/// unmapped members on inbound requests.
|
||||
/// </remarks>
|
||||
public sealed class SerializationTests
|
||||
{
|
||||
// Deliberately the shared options, not a hand-rolled instance: constructing options
|
||||
// separately is exactly the mistake these contracts must not permit.
|
||||
private static JsonSerializerOptions Options => DodoSshJsonContext.ResponseOptions;
|
||||
|
||||
[Fact]
|
||||
public void Properties_AreCamelCase()
|
||||
{
|
||||
var payload = new EncryptedPayload([1, 2, 3], KeyGeneration: 4, AadVersion: 1);
|
||||
|
||||
var json = JsonSerializer.Serialize(payload, Options);
|
||||
|
||||
json.ShouldContain("\"envelope\"");
|
||||
json.ShouldContain("\"keyGeneration\"");
|
||||
json.ShouldContain("\"aadVersion\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ByteArrays_AreBase64()
|
||||
{
|
||||
var payload = new EncryptedPayload([0xDE, 0xAD, 0xBE, 0xEF], 1, 1);
|
||||
|
||||
var json = JsonSerializer.Serialize(payload, Options);
|
||||
|
||||
json.ShouldContain(Convert.ToBase64String([0xDE, 0xAD, 0xBE, 0xEF]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enums_AreSerialisedAsStrings()
|
||||
{
|
||||
// Integers across the wire would make a reordered enum silently reinterpret data, and
|
||||
// would make captured payloads unreadable without the matching build.
|
||||
var change = new SyncChange(
|
||||
SyncEntityType.Host,
|
||||
Guid.CreateVersion7(),
|
||||
SyncOperation.Upsert,
|
||||
Version: 1,
|
||||
ChangeSequence: 42,
|
||||
Payload: null,
|
||||
PlaintextFields: null,
|
||||
UpdatedAt: DateTimeOffset.UnixEpoch);
|
||||
|
||||
var json = JsonSerializer.Serialize(change, Options);
|
||||
|
||||
json.ShouldContain("\"Host\"");
|
||||
json.ShouldContain("\"Upsert\"");
|
||||
json.ShouldNotContain("\"entityType\":1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptedPayload_RoundTrips()
|
||||
{
|
||||
var original = new EncryptedPayload([9, 8, 7, 6, 5], 12, 1);
|
||||
|
||||
var restored = JsonSerializer.Deserialize<EncryptedPayload>(
|
||||
JsonSerializer.Serialize(original, Options), Options);
|
||||
|
||||
restored.ShouldNotBeNull();
|
||||
restored.Envelope.ShouldBe(original.Envelope);
|
||||
restored.KeyGeneration.ShouldBe(original.KeyGeneration);
|
||||
restored.AadVersion.ShouldBe(original.AadVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SyncPushRequest_RoundTripsWithAllFields()
|
||||
{
|
||||
var original = new SyncPushRequest(
|
||||
[
|
||||
new SyncPushOperation(
|
||||
Guid.CreateVersion7(),
|
||||
SyncEntityType.Host,
|
||||
Guid.CreateVersion7(),
|
||||
SyncOperation.Upsert,
|
||||
ExpectedVersion: 3,
|
||||
Payload: new EncryptedPayload([1, 2, 3], 2, 1),
|
||||
PlaintextFields: new SyncPlaintextFields(
|
||||
RelayEnabled: true,
|
||||
Hostname: "bastion.internal",
|
||||
Port: 22)),
|
||||
new SyncPushOperation(
|
||||
Guid.CreateVersion7(),
|
||||
SyncEntityType.Credential,
|
||||
Guid.CreateVersion7(),
|
||||
SyncOperation.Delete,
|
||||
ExpectedVersion: 7,
|
||||
Payload: null,
|
||||
PlaintextFields: null),
|
||||
]);
|
||||
|
||||
var restored = JsonSerializer.Deserialize<SyncPushRequest>(
|
||||
JsonSerializer.Serialize(original, Options), Options);
|
||||
|
||||
restored.ShouldNotBeNull();
|
||||
restored.Operations.Count.ShouldBe(2);
|
||||
restored.Operations[0].PlaintextFields!.Hostname.ShouldBe("bastion.internal");
|
||||
restored.Operations[0].PlaintextFields!.RelayEnabled.ShouldBeTrue();
|
||||
restored.Operations[1].Payload.ShouldBeNull();
|
||||
restored.Operations[1].Operation.ShouldBe(SyncOperation.Delete);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullFields_AreOmitted()
|
||||
{
|
||||
var request = new SyncPullRequest(Cursor: null, Limit: null, EntityTypes: null);
|
||||
|
||||
var json = JsonSerializer.Serialize(request, Options);
|
||||
|
||||
json.ShouldNotContain("cursor");
|
||||
json.ShouldNotContain("limit");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StrictRequestOptions_RejectUnmappedMembers()
|
||||
{
|
||||
// A renamed or misspelled client property must surface as a 400, not as a silently
|
||||
// missing value that later looks like data loss.
|
||||
const string Json = """
|
||||
{"cursor":null,"limit":50,"entityTypes":null,"unexpectedProperty":"surprise"}
|
||||
""";
|
||||
|
||||
Should.Throw<JsonException>(() =>
|
||||
JsonSerializer.Deserialize<SyncPullRequest>(Json, DodoSshJsonContext.StrictRequestOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StrictRequestOptions_AcceptAWellFormedRequest()
|
||||
{
|
||||
const string Json = """
|
||||
{"cursor":"abc","limit":50}
|
||||
""";
|
||||
|
||||
var request = JsonSerializer.Deserialize<SyncPullRequest>(
|
||||
Json, DodoSshJsonContext.StrictRequestOptions);
|
||||
|
||||
request.ShouldNotBeNull();
|
||||
request.Cursor.ShouldBe("abc");
|
||||
request.Limit.ShouldBe(50);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseOptions_TolerateUnknownMembers()
|
||||
{
|
||||
// Forward compatibility: an older client must still read a newer server's response
|
||||
// rather than failing on a field it does not know about.
|
||||
const string Json = """
|
||||
{"envelope":"AQID","keyGeneration":1,"aadVersion":1,"futureField":true}
|
||||
""";
|
||||
|
||||
var payload = JsonSerializer.Deserialize<EncryptedPayload>(Json, Options);
|
||||
|
||||
payload.ShouldNotBeNull();
|
||||
payload.KeyGeneration.ShouldBe(1u);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberHandling_IsStrict()
|
||||
{
|
||||
// A quoted number would let a sloppy client send "1" where 1 is meant, which then
|
||||
// diverges between implementations.
|
||||
const string Json = """
|
||||
{"envelope":"AQID","keyGeneration":"1","aadVersion":1}
|
||||
""";
|
||||
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<EncryptedPayload>(Json, Options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetaResponse_RoundTrips()
|
||||
{
|
||||
var original = new MetaResponse(
|
||||
ServerVersion: "0.1.0",
|
||||
ApiVersions: [1],
|
||||
SyncProtocolVersion: 1,
|
||||
CryptoSpecVersion: 1,
|
||||
Features: ["relay", "teams"],
|
||||
MinClientVersion: "0.1.0",
|
||||
MaxOperationsPerPush: 500,
|
||||
MaxPayloadBytes: 8 * 1024 * 1024,
|
||||
MaxItemPayloadBytes: 256 * 1024);
|
||||
|
||||
var restored = JsonSerializer.Deserialize<MetaResponse>(
|
||||
JsonSerializer.Serialize(original, Options), Options);
|
||||
|
||||
restored.ShouldNotBeNull();
|
||||
restored.Features.ShouldBe(["relay", "teams"]);
|
||||
restored.MaxPayloadBytes.ShouldBe(8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RelayTicketRequest_HasNoAddressField()
|
||||
{
|
||||
// Structural guard on ADR 0004: if a client could name its own target, the relay
|
||||
// would become an authenticated open TCP proxy into the operator's network.
|
||||
var properties = typeof(RelayTicketRequest)
|
||||
.GetProperties()
|
||||
.Select(p => p.Name)
|
||||
.ToList();
|
||||
|
||||
properties.ShouldBe(["HostId", "PortForwardId"], ignoreOrder: true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user