feat: hardware binding modes, operator management, packaged SDK

- Hardware binding: License.HwBinding (none | fixed | firstActivation).
  firstActivation leaves the license unbound until the first activation,
  which claims the machine; afterwards it behaves like fixed. Legacy rows
  backfilled to fixed when a fingerprint was already set.
- Rebind: POST /panel/licenses/{id}/rebind moves a license to new
  hardware (or releases the binding), deactivating existing activations
  and re-signing a new version; 409 on revoked/expired. Operator or
  company admin. Audited, emits license.rebound.
- Operator management: list, invite, promote and demote operators
  (operator-only), guarding self-demotion and the last operator — until
  now a second operator could only be seeded directly into the database.
- SDK packaging: CertifiEd.Client is now a proper NuGet package (id,
  version, MIT license, README, XML docs, symbols) and gained the
  missing public XML documentation.
This commit is contained in:
Faris Laptop
2026-07-20 14:16:51 +05:00
parent 49725ddad5
commit e0354458df
4 changed files with 155 additions and 1 deletions
+18
View File
@@ -35,6 +35,9 @@ public sealed class CertifiEdLicenseClient : IAsyncDisposable
private DateTimeOffset _lastValidatedAt;
private bool _disposed;
/// <summary>Creates a client for a single local license file.</summary>
/// <param name="options">Server URL, license path and the Ed25519 public key.</param>
/// <param name="http">Optional shared <see cref="HttpClient"/>; when omitted the client owns and disposes its own.</param>
public CertifiEdLicenseClient(CertifiEdClientOptions options, HttpClient? http = null)
{
ArgumentNullException.ThrowIfNull(options);
@@ -189,6 +192,10 @@ public sealed class CertifiEdLicenseClient : IAsyncDisposable
return true;
}
/// <summary>
/// Stops the heartbeat loop and best-effort deactivates this machine, freeing its seat.
/// Safe to call more than once.
/// </summary>
public async ValueTask DisposeAsync()
{
if (_disposed)
@@ -431,6 +438,7 @@ public sealed class CertifiEdLicenseClient : IAsyncDisposable
// ── Options ───────────────────────────────────────────────────────────────────
/// <summary>Configuration for <see cref="CertifiEdLicenseClient"/>.</summary>
public sealed class CertifiEdClientOptions
{
/// <summary>Base URL of the CertifiEd server, e.g. "https://licensing.example.com".</summary>
@@ -448,12 +456,22 @@ public sealed class CertifiEdClientOptions
// ── Status ────────────────────────────────────────────────────────────────────
/// <summary>Local license state, derived from the token and the cached heartbeat marker.</summary>
public enum CertifiEdLicenseStatus
{
/// <summary>No license loaded, or its signature did not verify.</summary>
Invalid,
/// <summary>Licensed — the token is valid and within its offline window.</summary>
Active,
/// <summary>Offline window elapsed; still usable for a short grace period.</summary>
GracePeriod,
/// <summary>The license term or the grace period has run out.</summary>
Expired,
/// <summary>The license was revoked by the issuer.</summary>
Revoked,
}