# Solid Compatibility Layer — Implementation Map (Refined) This document maps the **exact file paths and insertion points** for adding **minimal Solid support** (WebID resolution + Solid-OIDC Client ID Document) without breaking existing Soulseek/mesh identity flows. This is a **security-first, minimal MVP** that can be extended later. **Reference ticket:** Add optional Solid WebID + Solid-OIDC "Pod Metadata" integration (zero breakage) **Non-goals:** Do not change the signed structure of `PeerProfile` or `FriendInvite`; do not require Pod/WebID for core features; do not implement full OIDC flow in MVP (just Client ID doc + WebID resolution). --- ## 1. Backend: Options (Feature flag + Solid block) ### File: `src/slskd/Core/Options.cs` **1.1 Add Feature flag** - **Location:** Inside `FeatureOptions` class. - **Insert after line 1445** (after `IdentityFriends` property): ```csharp /// /// Enable Solid / WebID / Solid-OIDC integration. When false, Solid APIs return 404. /// public bool Solid { get; init; } = false; ``` **1.2 Add root Solid property** - **Insert between lines 352 and 354** (after `Feature`, before `Sharing`): ```csharp /// /// Gets Solid options (WebID, Solid-OIDC, Pod metadata). /// [Validate] public SolidOptions Solid { get; init; } = new SolidOptions(); ``` **1.3 Add SolidOptions class** - **Insert between lines 1465 and 1466** (after `ScenePodBridgeOptions` ends, before `SharingOptions` begins): ```csharp /// /// Solid options (WebID, Solid-OIDC, Pod metadata). /// public class SolidOptions { /// /// If true, allow plain http:// WebID/Pod URLs (ONLY for dev/test). Keep false in prod. /// public bool AllowInsecureHttp { get; init; } = false; /// Max bytes we will read from WebID profile / Pod metadata resources. public int MaxFetchBytes { get; init; } = 1_000_000; /// HTTP timeout for WebID/Pod fetches. public int TimeoutSeconds { get; init; } = 10; /// /// Allowed Pod/WebID hostnames (exact match). Empty = deny all remote fetches unless explicitly set. /// public string[] AllowedHosts { get; init; } = Array.Empty(); /// /// Where the Solid-OIDC Client ID document is served from (default: /solid/clientid.jsonld). /// Leave empty to auto-derive from request base URL. /// public string? ClientIdUrl { get; init; } /// /// Redirect URI path used for Solid-OIDC (default: /solid/callback). (Callback wiring can come later.) /// public string RedirectPath { get; init; } = "/solid/callback"; } ``` **Do not:** Change any existing option property names or types used for binding. --- ## 2. Backend: Add dotNetRDF dependency ### File: `src/slskd/slskd.csproj` - **Insert in existing ``** with other `PackageReference` entries (e.g. after line ~156): ```xml ``` --- ## 3. Backend: Program.cs wire-up ### File: `src/slskd/Program.cs` **3.1 Register Solid services** - **Insert after line 2071** (in `ConfigureDependencyInjectionContainer`, after Identity registrations): ```csharp // Solid / WebID / Solid-OIDC (optional; gated per-request by Feature.Solid) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); ``` **3.2 Add anonymous endpoint for Client ID Document** - **Insert between lines 2696 and 2698** (after `endpoints.MapControllers();`, before health mapping): ```csharp endpoints.MapGet("/solid/clientid.jsonld", async context => { var opts = context.RequestServices.GetRequiredService>(); if (!opts.CurrentValue.Feature.Solid) { context.Response.StatusCode = StatusCodes.Status404NotFound; return; } var svc = context.RequestServices.GetRequiredService(); context.Response.ContentType = "application/ld+json"; await svc.WriteClientIdDocumentAsync(context, context.RequestAborted).ConfigureAwait(false); }).AllowAnonymous(); ``` --- ## 4. Backend: Solid module (new files) **Create folder:** `src/slskd/Solid/` ### 4.1 Client ID Document Service **New file:** `src/slskd/Solid/ISolidClientIdDocumentService.cs` ```csharp namespace slskd.Solid; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; public interface ISolidClientIdDocumentService { Task WriteClientIdDocumentAsync(HttpContext http, CancellationToken ct); } ``` **New file:** `src/slskd/Solid/SolidClientIdDocumentService.cs` - Must generate compliant JSON-LD with `@context` and OIDC client metadata. - See refined ticket section 4B for full implementation (derives base URL, uses `SolidOptions.ClientIdUrl` and `RedirectPath`). ### 4.2 SSRF hardening policy **New file:** `src/slskd/Solid/ISolidFetchPolicy.cs` ```csharp namespace slskd.Solid; using System; using System.Threading; using System.Threading.Tasks; public interface ISolidFetchPolicy { Task ValidateAsync(Uri uri, CancellationToken ct); } ``` **New file:** `src/slskd/Solid/SolidFetchPolicy.cs` - Enforces: HTTPS only (unless `AllowInsecureHttp`), host allow-list (`AllowedHosts`), blocks localhost/.local/private IPs. - See refined ticket section 4C for full implementation. ### 4.3 WebID resolver **New file:** `src/slskd/Solid/ISolidWebIdResolver.cs` ```csharp namespace slskd.Solid; using System; using System.Threading; using System.Threading.Tasks; public interface ISolidWebIdResolver { Task ResolveAsync(Uri webId, CancellationToken ct); } public sealed record SolidWebIdProfile(Uri WebId, Uri[] OidcIssuers); ``` **New file:** `src/slskd/Solid/SolidWebIdResolver.cs` - Uses `ISolidFetchPolicy` to validate URI. - Fetches with timeout + max bytes (`MaxFetchBytes`, `TimeoutSeconds`). - Parses RDF (dotNetRDF) to locate `solid:oidcIssuer`. - See refined ticket section 4D for full implementation. --- ## 5. Backend: API controller **New file:** `src/slskd/Solid/API/SolidController.cs` - **Route:** `[Route("api/v{version:apiVersion}/solid")]`, `[ApiVersion("0")]` - **Auth:** `[Authorize(Policy = AuthPolicy.Any)]`, `[ValidateCsrfForCookiesOnly]` - **Feature gate:** Every action checks `if (!Enabled) return NotFound();` where `Enabled => _options.CurrentValue.Feature.Solid` - **Endpoints:** - `GET /api/v0/solid/status` — returns enabled status, clientId, redirectPath - `POST /api/v0/solid/resolve-webid` — body `{ webId: string }`, returns `{ webId, oidcIssuers: string[] }` - **Reference pattern:** `src/slskd/Identity/API/ContactsController.cs` (Enabled property, NotFound when disabled). See refined ticket section 5 for full controller implementation. --- ## 6. Web UI ### 6.1 New component **New folder:** `src/web/src/components/Solid/` **New file:** `src/web/src/components/Solid/SolidSettings.jsx` - Shows status (enabled/disabled), Client ID, redirect path. - WebID input field + "Resolve WebID" button. - Displays resolved OIDC issuers. - Warns about AllowedHosts requirement. See refined ticket section 6D for full component implementation. ### 6.2 Wire into App.jsx **File:** `src/web/src/components/App.jsx` **6.2.1 Import** - **Insert between lines 25 and 26** (after other component imports): ```jsx import SolidSettings from './Solid/SolidSettings'; ``` **6.2.2 Navigation link** - **Insert between lines 458 and 459** (between Contacts and Collections menu links): ```jsx Solid ``` **6.2.3 Route** - **Insert between lines 672 and 673** (after `/collections` route closes, before `/searches` route begins): ```jsx this.withTokenCheck(
, ) } /> ``` --- ## 7. Config example ### File: `config/slskd.example.yml` - **Location:** Under the existing `# feature:` block (starts ~line 225). Add commented entries: ```yaml # Solid: false # Enable Solid (WebID, Solid-OIDC) (default: false) # solid: # allowedHosts: [] # Empty = deny all remote fetches (SSRF safety) # timeoutSeconds: 10 # maxFetchBytes: 1000000 # allowInsecureHttp: false # ONLY for dev/test # redirectPath: "/solid/callback" ``` **Important:** `allowedHosts` empty = deny all remote fetches. This is intentional for SSRF safety. --- ## 8. Tests ### 8.1 Unit tests (backend) **Project:** `tests/slskd.Tests.Unit/` **New folder:** `tests/slskd.Tests.Unit/Solid/` - **Client ID doc endpoint:** Returns 404 when `Feature.Solid=false`, returns `application/ld+json` when true, validates JSON-LD shape. - **SolidFetchPolicy:** Blocks `http://` when `AllowInsecureHttp=false`, blocks `https://localhost/...`, blocks hosts not in `AllowedHosts`, blocks private IPs. - **SolidWebIdResolver:** Response-size limit triggers for > `MaxFetchBytes`, parses Turtle/JSON-LD to extract `solid:oidcIssuer`. ### 8.2 E2E / integration (CI-safe) - **Do not** depend on a real Pod in CI. - Add a **fake Solid server** in test harness: serves WebID doc (Turtle) from a local allowed host. - Playwright test: enable `Feature.Solid` + set `AllowedHosts` to include local mock host, navigate `/solid`, resolve WebID, assert issuers list renders. --- ## 9. Critical "do not" checklist - **Do not** change `PeerProfile` or `FriendInvite` signed structure or schema. - **Do not** log tokens, authorization codes, DPoP proofs, or full `Authorization` headers. - **Do not** require WebID/Pod for core Soulseek or mesh features; everything works without Solid. - **Do not** allow remote fetches without explicit `AllowedHosts` (empty list = deny all). - **Do not** allow `http://` by default (only with `AllowInsecureHttp=true` for dev/test). - **Do not** add automatic builds on push; builds remain tag-only per project rules. - **Do not** implement full OIDC flow in MVP (just Client ID doc + WebID resolution). --- ## 10. File and folder summary | Item | Path | |------|------| | Feature + Solid options | `src/slskd/Core/Options.cs` (lines 352-354, 1445, 1465-1466) | | Solid module root | `src/slskd/Solid/` | | Client ID doc service | `src/slskd/Solid/ISolidClientIdDocumentService.cs`, `SolidClientIdDocumentService.cs` | | SSRF policy | `src/slskd/Solid/ISolidFetchPolicy.cs`, `SolidFetchPolicy.cs` | | WebID resolver | `src/slskd/Solid/ISolidWebIdResolver.cs`, `SolidWebIdResolver.cs` | | API controller | `src/slskd/Solid/API/SolidController.cs` | | Client ID doc route | `Program.cs` lines 2696-2698 (`MapGet` with `AllowAnonymous`) | | Program.cs registrations | After line 2071 (Identity services) | | NuGet dependency | `src/slskd/slskd.csproj` (dotNetRDF ~3.4.1) | | Web UI component | `src/web/src/components/Solid/SolidSettings.jsx` | | App.jsx imports | Line 25-26 (import) | | App.jsx nav link | Line 458-459 (menu item) | | App.jsx route | Line 672-673 (Route component) | | Example config | `config/slskd.example.yml` (feature + solid block) | | Unit tests | `tests/slskd.Tests.Unit/Solid/` | --- ## 11. Differences from original ticket This refined map reflects a **minimal MVP** that: 1. **Focuses on WebID resolution + Client ID Document** (not full OIDC flow yet). 2. **Adds explicit SSRF hardening** (`SolidFetchPolicy` with host allow-list, HTTPS enforcement, private IP blocking). 3. **Simplifies SolidOptions** (no `AppContainerName`, `PreferSaiRegistry`, `PublishPublicPeerCard` — those come later). 4. **Uses exact line anchors** from current codebase (352/354, 1445, 1465/1466 for Options.cs; 2071, 2696-2698 for Program.cs; 25/26, 458/459, 672/673 for App.jsx). **Future extensions** (not in MVP): - Full OIDC Authorization Code + PKCE flow - Token store (encrypted via Data Protection) - DPoP proof generation - Pod metadata read/write (playlists, sharelists) - Type Index / SAI registry discovery - Access control (WAC/ACP) writers These can be added incrementally after the MVP is working. --- Use this map together with the refined Solid ticket for implementation. The refined ticket includes full code implementations for each service file.