Enhance configuration and routing for streaming features: update .env.example and appsettings.json to include new scheduler and streaming options, add streaming endpoint mapping in Program.cs, and integrate streaming services in DependencyInjection. Introduce segment path resolution in MediaPathResolver for asset management.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TeleWave.Infrastructure.Identity;
|
||||
|
||||
namespace TeleWave.Infrastructure.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Короткоживущий подписанный токен для cookie <c>tw_stream</c>: выдаётся авторизованному зрителю,
|
||||
/// проверяется на запросах плейлиста и сегментов (работает и с нативным HLS, где заголовок не
|
||||
/// поставить). Подпись — HMAC-SHA256 на том же ключе, что и JWT.
|
||||
/// </summary>
|
||||
public sealed class StreamTokenService(IOptions<JwtOptions> jwtOptions)
|
||||
{
|
||||
private static readonly TimeSpan Ttl = TimeSpan.FromHours(6);
|
||||
private readonly byte[] _key = Encoding.UTF8.GetBytes(jwtOptions.Value.SigningKey);
|
||||
|
||||
public (string Token, DateTimeOffset ExpiresAt) Issue(Guid userId)
|
||||
{
|
||||
var expiresAt = DateTimeOffset.UtcNow.Add(Ttl);
|
||||
var payload = $"{userId:N}.{expiresAt.ToUnixTimeSeconds()}";
|
||||
var token = $"{Base64Url(Encoding.UTF8.GetBytes(payload))}.{Base64Url(Sign(payload))}";
|
||||
return (token, expiresAt);
|
||||
}
|
||||
|
||||
public bool Validate(string? token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
return false;
|
||||
|
||||
var parts = token.Split('.');
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
byte[] payloadBytes;
|
||||
byte[] signature;
|
||||
try
|
||||
{
|
||||
payloadBytes = FromBase64Url(parts[0]);
|
||||
signature = FromBase64Url(parts[1]);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var payload = Encoding.UTF8.GetString(payloadBytes);
|
||||
var expected = Sign(payload);
|
||||
if (!CryptographicOperations.FixedTimeEquals(signature, expected))
|
||||
return false;
|
||||
|
||||
var dot = payload.LastIndexOf('.');
|
||||
if (dot < 0 || !long.TryParse(payload.AsSpan(dot + 1), out var expiresUnix))
|
||||
return false;
|
||||
|
||||
return DateTimeOffset.FromUnixTimeSeconds(expiresUnix) > DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
private byte[] Sign(string payload) =>
|
||||
HMACSHA256.HashData(_key, Encoding.UTF8.GetBytes(payload));
|
||||
|
||||
private static string Base64Url(byte[] bytes) =>
|
||||
Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
||||
|
||||
private static byte[] FromBase64Url(string value)
|
||||
{
|
||||
var padded = value.Replace('-', '+').Replace('_', '/');
|
||||
padded += (padded.Length % 4) switch
|
||||
{
|
||||
2 => "==",
|
||||
3 => "=",
|
||||
_ => "",
|
||||
};
|
||||
return Convert.FromBase64String(padded);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user