mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
caa6952ae7
A receiver holds one pairing, so a Global Admin whose phone is paired in the platform scope could not get their own tenant's enrollment alerts without replacing that pairing. The push transport now resolves a tenant channel's recipients as the tenant's Active devices plus the Active platform devices whose OwnerHomeTenantId is that tenant, filtered by the platform eligibility rule and deduplicated by endpoint; no other tenant reaches a platform device, there is no opt-in and no setting. The home tenant's device list shows such a row with PushDeviceDto.Scope = "platform" (manifest and wire types regenerated); the panel marks it with a Platform badge and no actions — it is managed under the platform's device list, and the tenant routes do not know the id. Tests: fan-out incl. a foreign home tenant, a former Global Admin and a shared endpoint; the list in the home tenant, the owner's and an operator's view, and the refusing tenant routes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
157 lines
7.5 KiB
C#
157 lines
7.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AutopilotMonitor.Shared.Models
|
|
{
|
|
// Web Push pairing + devices (Functions/Push). Declaration order == wire order.
|
|
|
|
/// <summary>POST push/pairings · POST global/push/pairings — a fresh pairing code for the caller's own person.</summary>
|
|
public class CreatePairingResponse : IApiResponse
|
|
{
|
|
/// <summary>Handle for the PC-side status poll and the confirm/reject calls.</summary>
|
|
public string PairingId { get; set; } = default!;
|
|
/// <summary>The 11-character code to type into the receiver app.</summary>
|
|
public string Code { get; set; } = default!;
|
|
/// <summary>The same code as the pairing link (QR payload): …/push/pair/#p=<code>.</summary>
|
|
public string Url { get; set; } = default!;
|
|
public DateTime ExpiresUtc { get; set; }
|
|
}
|
|
|
|
/// <summary>GET push/pairings/{pairingId} — what the receiver has done with the code so far.</summary>
|
|
public class PairingStatusResponse : IApiResponse
|
|
{
|
|
/// <summary>Pending · Redeemed (a device waits for confirmation) · Confirmed · Rejected · Expired.</summary>
|
|
public string Status { get; set; } = default!;
|
|
/// <summary>The redeeming device, present from Redeemed on.</summary>
|
|
public PairingDeviceDto? Device { get; set; }
|
|
}
|
|
|
|
public class PairingDeviceDto
|
|
{
|
|
public string DeviceId { get; set; } = default!;
|
|
public string Label { get; set; } = default!;
|
|
public string Platform { get; set; } = default!;
|
|
public DateTime RedeemedUtc { get; set; }
|
|
}
|
|
|
|
/// <summary>POST push/pairings/{pairingId}/confirm — the device is Active from now on.</summary>
|
|
public class ConfirmPairingResponse : IApiResponse
|
|
{
|
|
public string DeviceId { get; set; } = default!;
|
|
}
|
|
|
|
/// <summary>POST push/pair/begin — the receiver validates its code before asking for notification permission.</summary>
|
|
public class BeginPairRequest : IApiRequest
|
|
{
|
|
public string Code { get; set; } = default!;
|
|
}
|
|
|
|
/// <summary>The VAPID application server key the receiver must subscribe with, and its identity.</summary>
|
|
public class BeginPairResponse : IApiResponse
|
|
{
|
|
public string Kid { get; set; } = default!;
|
|
/// <summary>Uncompressed P-256 point, base64url — pushManager.subscribe({applicationServerKey}).</summary>
|
|
public string VapidPublicKey { get; set; } = default!;
|
|
}
|
|
|
|
/// <summary>POST push/pair — the receiver hands over its subscription; the device is created Pending.</summary>
|
|
public class RedeemPairRequest : IApiRequest
|
|
{
|
|
public string Code { get; set; } = default!;
|
|
public string Kid { get; set; } = default!;
|
|
public string Endpoint { get; set; } = default!;
|
|
public string P256dh { get; set; } = default!;
|
|
public string Auth { get; set; } = default!;
|
|
public string? Label { get; set; }
|
|
public string? Platform { get; set; }
|
|
public string? AppVersion { get; set; }
|
|
}
|
|
|
|
public class RedeemPairResponse : IApiResponse
|
|
{
|
|
/// <summary>"{scope}.{deviceId}.{secret}" — the receiver's only credential; header X-Push-Device-Token.</summary>
|
|
public string DeviceToken { get; set; } = default!;
|
|
public string DeviceId { get; set; } = default!;
|
|
/// <summary>Always Pending here; the PC confirms.</summary>
|
|
public string Status { get; set; } = default!;
|
|
/// <summary>Suggested interval for the receiver's status poll while Pending.</summary>
|
|
public int PollSeconds { get; set; }
|
|
}
|
|
|
|
/// <summary>GET/PUT push/device — the receiver's own view of its registration.</summary>
|
|
public class PushDeviceStatusResponse : IApiResponse
|
|
{
|
|
public string DeviceId { get; set; } = default!;
|
|
/// <summary>Pending · Active · Paused · Stale (a revoked device is gone: the token answers 401).</summary>
|
|
public string Status { get; set; } = default!;
|
|
public string Label { get; set; } = default!;
|
|
public string Platform { get; set; } = default!;
|
|
public string Kid { get; set; } = default!;
|
|
public DateTime PairedUtc { get; set; }
|
|
public DateTime? ConfirmedUtc { get; set; }
|
|
public DateTime? LastDeliveredUtc { get; set; }
|
|
/// <summary>The owner's UPN — this is the owner's own device.</summary>
|
|
public string OwnerUpn { get; set; } = default!;
|
|
/// <summary>Tenant display name, or "Platform operator" for the platform scope.</summary>
|
|
public string ScopeName { get; set; } = default!;
|
|
/// <summary>
|
|
/// The platform's current VAPID key id. Differs from <see cref="Kid"/> after a key rotation:
|
|
/// the receiver then subscribes again with <see cref="ActiveVapidPublicKey"/> and PUTs the
|
|
/// new subscription with this kid — no re-pairing. Empty while the channel is unconfigured.
|
|
/// </summary>
|
|
public string ActiveKid { get; set; } = default!;
|
|
/// <summary>Uncompressed P-256 point, base64url, of the current key (what to subscribe with).</summary>
|
|
public string ActiveVapidPublicKey { get; set; } = default!;
|
|
}
|
|
|
|
/// <summary>PUT push/device — a changed or re-created subscription (same device, same owner).</summary>
|
|
public class ResubscribeRequest : IApiRequest
|
|
{
|
|
public string Endpoint { get; set; } = default!;
|
|
public string P256dh { get; set; } = default!;
|
|
public string Auth { get; set; } = default!;
|
|
public string Kid { get; set; } = default!;
|
|
}
|
|
|
|
/// <summary>GET push/devices · GET global/push/devices — own devices (everyone) plus every device of the scope (admins).</summary>
|
|
public class PushDeviceListResponse : IApiResponse
|
|
{
|
|
public IReadOnlyList<PushDeviceDto> Devices { get; set; } = default!;
|
|
}
|
|
|
|
public class PushDeviceDto
|
|
{
|
|
public string DeviceId { get; set; } = default!;
|
|
public string Label { get; set; } = default!;
|
|
public string Platform { get; set; } = default!;
|
|
public string Status { get; set; } = default!;
|
|
public string Kid { get; set; } = default!;
|
|
public DateTime PairedUtc { get; set; }
|
|
public DateTime? ConfirmedUtc { get; set; }
|
|
public DateTime? LastDeliveredUtc { get; set; }
|
|
public DateTime? LastOpenedUtc { get; set; }
|
|
public string OwnerUpn { get; set; } = default!;
|
|
/// <summary>True for the caller's own devices.</summary>
|
|
public bool IsOwn { get; set; }
|
|
/// <summary>
|
|
/// "tenant": paired in this scope. "platform": a Global Administrator's platform device, listed
|
|
/// in their home tenant because it receives this tenant's alerts without a second pairing
|
|
/// (D-334); the tenant routes do not act on it — it is managed under the platform's device list.
|
|
/// </summary>
|
|
public string Scope { get; set; } = "tenant";
|
|
}
|
|
|
|
/// <summary>GET/PUT/DELETE sessions/{sessionId}/watch — "notify my devices when this session ends".</summary>
|
|
public class SessionWatchResponse : IApiResponse
|
|
{
|
|
public bool Watching { get; set; }
|
|
public DateTime? ExpiresUtc { get; set; }
|
|
/// <summary>
|
|
/// Whether this caller can watch at all: an enabled Push channel in one of the caller's own
|
|
/// scopes (home tenant; the platform for a Global Admin) where the caller holds an Admin or
|
|
/// Operator role. False hides the portal's button instead of letting a click fail.
|
|
/// </summary>
|
|
public bool Available { get; set; }
|
|
}
|
|
}
|