Public Access
Restructure into src/tests and add build foundation (M0)
Moves the scaffold to src/DodoSSH.Api and establishes the repo conventions the rest
of the milestones build on.
Structure:
- src/{Contracts,Crypto,Domain,Infrastructure,Api}, tests/{Contracts,Crypto,Domain}.Tests
- DodoSSH.slnx rewritten with src/ and tests/ solution folders
Build:
- Directory.Build.props centralises TFM, nullable, deterministic builds and
TreatWarningsAsErrors; Directory.Packages.props pins every version centrally
- packages.lock.json committed so CI restores in locked mode
- NuGet.config clears machine-level sources, which both fixes NU1507 under central
package management and makes restore reproducible off this machine
- Microsoft.OpenApi pinned to 2.11.0: ASP.NET Core 10.0.10 resolves 2.0.0, which is
covered by GHSA-v5pm-xwqc-g5wc (high, patched in 2.7.5)
Analyzers:
- AnalysisLevel is Recommended, not All. With warnings-as-errors, All turns opinionated
naming rules into build breaks and trains people to blanket-suppress.
- BannedSymbols.txt bans DateTime.UtcNow (TimeProvider), Guid.NewGuid (CreateVersion7),
sync-over-async, MD5/SHA1, PBKDF2 and SecureString
- CA1711/CA1724 disabled: both are .NET Framework CAS-era naming rules
- PublicApiAnalyzers on Contracts only, since that assembly is the client's real contract
API:
- weather-forecast template removed
- UseHttpsRedirection removed; TLS terminates at the reverse proxy and redirecting
behind one causes loops
- /healthz/{live,ready,startup}. Liveness deliberately checks no dependencies so a
transient database outage cannot restart the container and kill live SSH sessions.
Notes:
- No coverage collector yet. Microsoft.Testing.Extensions.CodeCoverage pulls an MTP 1.x
MSBuild extension that throws TypeLoadException against the MTP 2.3.x xunit.v3 brings.
Coverage gates are an M3 concern; revisit with an MTP 2.x-aligned version then.
Verified: dotnet build (0 warnings), 17 tests pass, format check clean, API serves
health and OpenAPI endpoints.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<!--
|
||||
TargetFramework, Nullable, ImplicitUsings, analyzers and central package
|
||||
management all come from ../../Directory.Build.props.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Integration tests reach Program through WebApplicationFactory<Program>. -->
|
||||
<InternalsVisibleTo Include="DodoSSH.Api.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using DodoSSH.Api.Setup;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddDodoOpenApi();
|
||||
builder.Services.AddDodoHealthChecks();
|
||||
|
||||
// DateTime.UtcNow is banned repo-wide (see BannedSymbols.txt); everything takes
|
||||
// TimeProvider so time can be faked in tests.
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Deliberately no UseHttpsRedirection: the API is always fronted by a reverse proxy
|
||||
// (Caddy in the reference compose stack) which terminates TLS. Redirecting here
|
||||
// produces redirect loops behind a proxy. HTTPS in development comes from the
|
||||
// launch profile instead.
|
||||
|
||||
app.MapDodoHealthChecks();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
await app.RunAsync().ConfigureAwait(false);
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5233",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7217;http://localhost:5233",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace DodoSSH.Api.Setup;
|
||||
|
||||
/// <summary>
|
||||
/// Health check wiring.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The split between liveness and readiness is deliberate and load-bearing for the
|
||||
/// relay: <c>/healthz/live</c> checks the process and nothing else, so a transient
|
||||
/// PostgreSQL outage cannot cause the orchestrator to restart the container and
|
||||
/// guillotine every live SSH session. Dependency checks belong in
|
||||
/// <c>/healthz/ready</c>, which only removes the instance from load balancing.
|
||||
/// </remarks>
|
||||
internal static class HealthChecks
|
||||
{
|
||||
/// <summary>Tag for checks that gate readiness (dependencies).</summary>
|
||||
internal const string ReadyTag = "ready";
|
||||
|
||||
/// <summary>Tag for checks that gate startup completion.</summary>
|
||||
internal const string StartupTag = "startup";
|
||||
|
||||
internal static IServiceCollection AddDodoHealthChecks(this IServiceCollection services)
|
||||
{
|
||||
services.AddHealthChecks();
|
||||
|
||||
// Dependency checks are registered by the milestone that introduces the
|
||||
// dependency, each tagged ReadyTag:
|
||||
// M1 — PostgreSQL, OIDC discovery + JWKS reachability, pending migrations
|
||||
// M4 — Data Protection key ring readability
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static WebApplication MapDodoHealthChecks(this WebApplication app)
|
||||
{
|
||||
// Liveness: process is running and the pipeline responds. No dependencies.
|
||||
app.MapHealthChecks("/healthz/live", new HealthCheckOptions
|
||||
{
|
||||
Predicate = _ => false,
|
||||
}).AllowAnonymous();
|
||||
|
||||
// Readiness: safe to route traffic here.
|
||||
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
|
||||
{
|
||||
Predicate = registration => registration.Tags.Contains(ReadyTag),
|
||||
}).AllowAnonymous();
|
||||
|
||||
// Startup: one-time initialisation finished (K8s startupProbe).
|
||||
app.MapHealthChecks("/healthz/startup", new HealthCheckOptions
|
||||
{
|
||||
Predicate = registration => registration.Tags.Contains(StartupTag),
|
||||
}).AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace DodoSSH.Api.Setup;
|
||||
|
||||
/// <summary>
|
||||
/// OpenAPI document configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The generated document exists for third parties and a future CLI. It is emitted at
|
||||
/// build time to <c>artifacts/openapi/v1.json</c> and diffed in CI so an unintended
|
||||
/// contract change fails the pull request. The desktop client's actual contract is the
|
||||
/// <c>DodoSSH.Contracts</c> assembly, guarded by PublicApiAnalyzers.
|
||||
/// </remarks>
|
||||
internal static class OpenApi
|
||||
{
|
||||
internal const string DocumentName = "v1";
|
||||
|
||||
internal static IServiceCollection AddDodoOpenApi(this IServiceCollection services)
|
||||
{
|
||||
services.AddOpenApi(DocumentName);
|
||||
|
||||
// Added in M1, once there are endpoints to describe:
|
||||
// - a document transformer contributing the OAuth2 authorizationCode + PKCE
|
||||
// security scheme, so the document is usable from a generated client
|
||||
// - a schema transformer mapping byte[] to {type: string, format: byte},
|
||||
// since every ciphertext field crosses the wire as base64
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Meziantou.Analyzer": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.134, )",
|
||||
"resolved": "3.0.134",
|
||||
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.10, )",
|
||||
"resolved": "10.0.10",
|
||||
"contentHash": "d4Atx9IHq7JgX0F/h7Db+m9zAUzC+cKdI9k+OWnnyQIOUQtfvjIEuhvbjPigVMkAmPUgCbJ8Yp6M9ghUqHtJSQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.OpenApi": "2.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
|
||||
},
|
||||
"Microsoft.OpenApi": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.11.0, )",
|
||||
"resolved": "2.11.0",
|
||||
"contentHash": "/ignjfdeKT2SGLIR7QEv19KnI0rvoxRG/TYDOZdK9EsWLjKK9IK8i1Mo5NRm9PRV3i64DzlTqnIflWvoyfljLg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
DTOs shared between the API and the desktop client. This assembly, not the generated
|
||||
OpenAPI document, is the real client contract, so PublicApiAnalyzers is enabled here
|
||||
and only here: an accidental change to a public member becomes a build error rather
|
||||
than a runtime deserialisation failure on somebody's laptop.
|
||||
|
||||
Track additions in PublicAPI.Unshipped.txt; move them to PublicAPI.Shipped.txt when a
|
||||
version is released.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="PublicAPI.Shipped.txt" />
|
||||
<AdditionalFiles Include="PublicAPI.Unshipped.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace DodoSSH.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Stable machine-readable error codes returned in the <c>code</c> extension of an
|
||||
/// RFC 9457 ProblemDetails response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These live in Contracts so the client switches on constants rather than parsing prose.
|
||||
/// The values are part of the public contract: add freely, never rename or repurpose.
|
||||
/// </remarks>
|
||||
public static class ProblemCodes
|
||||
{
|
||||
/// <summary>The base URI that every problem <c>type</c> is formed under.</summary>
|
||||
public const string TypeBaseUri = "https://dodossh.dev/problems/";
|
||||
|
||||
/// <summary>A push operation's <c>expectedVersion</c> did not match the stored row.</summary>
|
||||
public const string VaultConflict = "vault-conflict";
|
||||
|
||||
/// <summary>The caller is authenticated but lacks the required permission.</summary>
|
||||
public const string Forbidden = "forbidden";
|
||||
|
||||
/// <summary>The sync cursor was malformed, or failed its integrity tag.</summary>
|
||||
public const string InvalidCursor = "invalid-cursor";
|
||||
|
||||
/// <summary>An <c>Idempotency-Key</c> was reused with a different request body.</summary>
|
||||
public const string IdempotencyKeyReuse = "idempotency-key-reuse";
|
||||
|
||||
/// <summary>The caller has not yet enrolled a public key, so no vault is reachable.</summary>
|
||||
public const string EnrollmentRequired = "enrollment-required";
|
||||
|
||||
/// <summary>Enrollment was attempted for a user who already holds a current key.</summary>
|
||||
public const string AlreadyEnrolled = "already-enrolled";
|
||||
|
||||
/// <summary>The relay refused the requested target. Never states why, to avoid a probe oracle.</summary>
|
||||
public const string RelayTargetRejected = "relay-target-rejected";
|
||||
|
||||
/// <summary>The relay ticket is expired, already used, or not valid for this node.</summary>
|
||||
public const string RelayTicketInvalid = "relay-ticket-invalid";
|
||||
|
||||
/// <summary>A per-user or per-node relay session limit was reached.</summary>
|
||||
public const string RelayLimitReached = "relay-limit-reached";
|
||||
|
||||
/// <summary>The client is older than the server's <c>minClientVersion</c>.</summary>
|
||||
public const string ClientTooOld = "client-too-old";
|
||||
|
||||
/// <summary>A push batch exceeded the operation count or payload size cap.</summary>
|
||||
public const string PushBatchTooLarge = "push-batch-too-large";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#nullable enable
|
||||
@@ -0,0 +1,14 @@
|
||||
#nullable enable
|
||||
DodoSSH.Contracts.ProblemCodes
|
||||
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!
|
||||
const DodoSSH.Contracts.ProblemCodes.Forbidden = "forbidden" -> string!
|
||||
const DodoSSH.Contracts.ProblemCodes.IdempotencyKeyReuse = "idempotency-key-reuse" -> string!
|
||||
const DodoSSH.Contracts.ProblemCodes.InvalidCursor = "invalid-cursor" -> string!
|
||||
const DodoSSH.Contracts.ProblemCodes.PushBatchTooLarge = "push-batch-too-large" -> string!
|
||||
const DodoSSH.Contracts.ProblemCodes.RelayLimitReached = "relay-limit-reached" -> string!
|
||||
const DodoSSH.Contracts.ProblemCodes.RelayTargetRejected = "relay-target-rejected" -> string!
|
||||
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!
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Meziantou.Analyzer": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.134, )",
|
||||
"resolved": "3.0.134",
|
||||
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.PublicApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "W4kJGezNIKLzo0Ak5FAQDFvkMf2U7DtGL4THmHyRSApfKsKt5V+eX/bU0ZLKAt/uf9Bb2o1bi0YDKj/GRB/vYQ=="
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.10, )",
|
||||
"resolved": "10.0.10",
|
||||
"contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace DodoSSH.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Constants of the DodoSSH cryptographic specification.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// docs/crypto.md is the normative specification; this type must agree with it exactly.
|
||||
/// The implementation of the envelope, AAD derivation and key wrapping lands in M1, once
|
||||
/// the specification and its test vectors are frozen. Nothing else may be built on top of
|
||||
/// an unfrozen AAD: only clients can re-encrypt, so a change after users hold data cannot
|
||||
/// be migrated server-side.
|
||||
/// </remarks>
|
||||
public static class CryptoSpec
|
||||
{
|
||||
/// <summary>Magic prefix identifying a DSH1 envelope.</summary>
|
||||
public const string EnvelopeMagic = "DSH1";
|
||||
|
||||
/// <summary>Version of the AAD derivation rule that payloads are bound to.</summary>
|
||||
/// <remarks>
|
||||
/// Stored per row as <c>payload_aad_version</c> so a future change can be applied
|
||||
/// lazily, re-encrypting on next write rather than in a migration.
|
||||
/// </remarks>
|
||||
public const short CurrentAadVersion = 1;
|
||||
|
||||
/// <summary>Domain-separation prefix for every AAD computation.</summary>
|
||||
public const string AadDomainPrefix = "dsh1\n";
|
||||
|
||||
/// <summary>Identifiers for the algorithms an envelope may declare.</summary>
|
||||
public enum AlgorithmId : byte
|
||||
{
|
||||
/// <summary>Reserved; never written.</summary>
|
||||
Unspecified = 0,
|
||||
|
||||
/// <summary>Symmetric content encryption under a known key.</summary>
|
||||
XChaCha20Poly1305 = 1,
|
||||
|
||||
/// <summary>Symmetric fallback where XChaCha20 is unavailable.</summary>
|
||||
Aes256Gcm = 2,
|
||||
|
||||
/// <summary>Anonymous-sender seal to an X25519 public key.</summary>
|
||||
SealToX25519 = 3,
|
||||
|
||||
// 4 is reserved for a hybrid X25519 + ML-KEM-768 seal. Store-now-decrypt-later is
|
||||
// a genuine threat for long-lived SSH keys, so the identifier is claimed now even
|
||||
// though the construction ships later.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
The DSH1 envelope format, AAD derivation, key wrapping and the KDF.
|
||||
Referenced by both the API and the client, but the server only ever uses the format
|
||||
and fingerprint constants: it never holds a key and never decrypts a payload.
|
||||
See docs/crypto.md, which is the normative specification for everything here.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="DodoSSH.Crypto.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Meziantou.Analyzer": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.134, )",
|
||||
"resolved": "3.0.134",
|
||||
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.10, )",
|
||||
"resolved": "10.0.10",
|
||||
"contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace DodoSSH.Domain.Authorization;
|
||||
|
||||
/// <summary>
|
||||
/// PermissionFlags a subject (user or team) may hold over a vault or an individual resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Evaluation is a plain union across the subject's direct grants and the grants held by
|
||||
/// teams they belong to. There are deliberately no Deny rules: union-only evaluation is
|
||||
/// monotonic and straightforward to test, and Deny can be added later additively if a
|
||||
/// real need appears. Express restriction by granting narrowly instead.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="Connect"/> is a user-interface hint, <b>not</b> a security boundary. SSH
|
||||
/// terminates on the client, so opening a session requires the credential's plaintext on
|
||||
/// that machine; "may connect but may not view the key" is therefore unenforceable in
|
||||
/// this architecture. Treat it as an anti-shoulder-surfing convenience and never document
|
||||
/// it as access control. See docs/adr/0001-e2ee-trust-model.md.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
public enum PermissionFlags
|
||||
{
|
||||
/// <summary>No access.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>May fetch and decrypt the resource. Implies the ability to use it.</summary>
|
||||
Read = 1 << 0,
|
||||
|
||||
/// <summary>May create, modify and soft-delete resources in the vault.</summary>
|
||||
Write = 1 << 1,
|
||||
|
||||
/// <summary>Intent hint that the subject uses this host for sessions. Not a boundary.</summary>
|
||||
Connect = 1 << 2,
|
||||
|
||||
/// <summary>May grant access to other subjects, which requires re-wrapping the vault key.</summary>
|
||||
Share = 1 << 3,
|
||||
|
||||
/// <summary>May administer the vault itself: rename, rekey, manage ACLs.</summary>
|
||||
Admin = 1 << 4,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
Entities, enums and invariants. No EF Core reference: persistence concerns live in
|
||||
DodoSSH.Infrastructure so the domain stays unit-testable with no database.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="DodoSSH.Domain.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Meziantou.Analyzer": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.134, )",
|
||||
"resolved": "3.0.134",
|
||||
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!--
|
||||
Persistence: DodoDbContext, IEntityTypeConfiguration implementations, migrations and
|
||||
query helpers. EF Core and Npgsql arrive in M1 with the first migration.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../DodoSSH.Domain/DodoSSH.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="DodoSSH.Infrastructure.Tests" />
|
||||
<InternalsVisibleTo Include="DodoSSH.Api.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Meziantou.Analyzer": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.134, )",
|
||||
"resolved": "3.0.134",
|
||||
"contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.6.0, )",
|
||||
"resolved": "5.6.0",
|
||||
"contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
|
||||
},
|
||||
"dodossh.domain": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user