using System.Security.Cryptography; namespace Halo.Core.Auth; /// Eine laufende Anmeldung per Code — für den Fernseher. /// /// /// /// Ein Passwort auf der Apple-TV-Fernbedienung einzutippen ist eine Zumutung, /// die man selbst jedes Mal spürt. Stattdessen: sechsstelliger Code auf dem /// Bildschirm, Bestätigung am Telefon. /// /// /// Der Code allein holt nie ein Token ab. Dafür gibt es den Abholschein, /// den nur das anfragende Gerät kennt und der hier nur als Fingerabdruck /// liegt. Ohne ihn könnte jemand sechsstellige Zahlen durchprobieren — eine /// Million Versuche sind nichts, und am Ende stünde ein fremdes Gerät in der /// Mediathek. /// /// public sealed class AuthCode { /// Kurz genug zum Vorlesen, lang genug zum Nichterraten. public const int CodeLength = 6; /// So lange, wie jemand braucht, um zum Telefon zu greifen. public static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(5); public Guid Id { get; init; } = Guid.CreateVersion7(); public required string Code { get; init; } public required string HandleFingerprint { get; init; } public required string DeviceName { get; init; } public required string DeviceId { get; init; } public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow; public required DateTimeOffset ExpiresAt { get; init; } /// Wer bestätigt hat — null, solange niemand. public Guid? ApprovedByUserId { get; set; } /// Wann das Token abgeholt wurde. Danach ist der Code verbraucht. public DateTimeOffset? ConsumedAt { get; set; } public bool IsExpired(DateTimeOffset now) => now >= ExpiresAt; /// Ob jetzt ein Token herausgegeben werden darf. public bool CanBeRedeemed(DateTimeOffset now) => ApprovedByUserId is not null && ConsumedAt is null && !IsExpired(now); /// Sechs Ziffern, gleichverteilt gezogen. /// /// /// und nicht : der /// Code ist ein Zugang. Die obere Grenze ist ausschliesslich, führende /// Nullen bleiben erhalten. /// public static string NewCode() => RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D" + CodeLength); }