Проведен аудит. Добавлено переключение треков

This commit is contained in:
FrigaT
2026-05-21 20:49:55 +03:00
parent 38af6174fa
commit 9139d8ecfe
23 changed files with 351 additions and 222 deletions

View File

@@ -90,10 +90,10 @@ public class FavoritesService
Creator = sp.Creator != null ? new Shared.Auth.ApplicationUserDto
{
Id = sp.Creator.Id,
UserName = sp.Creator.UserName,
Email = sp.Creator.Email,
UserName = sp.Creator.UserName ?? "",
Email = sp.Creator.Email ?? "",
YandexId = sp.Creator.YandexId,
DisplayName = sp.Creator.UserName
DisplayName = sp.Creator.UserName ?? ""
} : null
}).ToList();
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using PlaylistShared.Api.Entities;
using System.IdentityModel.Tokens.Jwt;
@@ -11,11 +12,15 @@ public class JwtService
{
private readonly IConfiguration _configuration;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ITimeLimitedDataProtector _playTokenProtector;
public JwtService(IConfiguration configuration, UserManager<ApplicationUser> userManager)
public JwtService(IConfiguration configuration, UserManager<ApplicationUser> userManager, IDataProtectionProvider dataProtectionProvider)
{
_configuration = configuration;
_userManager = userManager;
_playTokenProtector = dataProtectionProvider
.CreateProtector("AudioPlayToken")
.ToTimeLimitedDataProtector();
}
public async Task<(string Token, string RefreshToken, DateTime Expiration)> GenerateTokenAsync(ApplicationUser user)
@@ -71,4 +76,20 @@ public class JwtService
return null;
}
}
}
public string CreatePlayToken(Guid userId) =>
_playTokenProtector.Protect(userId.ToString(), TimeSpan.FromMinutes(5));
public Guid? ValidatePlayToken(string token)
{
try
{
var userId = _playTokenProtector.Unprotect(token);
return Guid.Parse(userId);
}
catch
{
return null;
}
}
}

View File

@@ -42,14 +42,6 @@ public class SharedPlaylistService
return MapToDto(entity);
}
public async Task<SharedPlaylistDto?> GetByTokenAsync(string token)
{
var entity = await _db.SharedPlaylists
.Include(sp => sp.Creator)
.FirstOrDefaultAsync(sp => sp.ShareToken == token && !sp.IsDeleted);
return entity == null ? null : MapToDto(entity);
}
public async Task<SharedPlaylist?> GetEntityByTokenAsync(string token)
{
return await _db.SharedPlaylists
@@ -142,8 +134,7 @@ public class SharedPlaylistService
.ToListAsync();
}
// Ручное маппинг сущности в DTO
private SharedPlaylistDto MapToDto(SharedPlaylist entity)
public SharedPlaylistDto MapToDto(SharedPlaylist entity)
{
return new SharedPlaylistDto
{

View File

@@ -43,4 +43,17 @@ public class TrackAdditionLogService
_db.TrackAdditionLogs.RemoveRange(logs);
await _db.SaveChangesAsync();
}
public async Task<Dictionary<string, string?>> GetAdditionUserNamesAsync(Guid sharedPlaylistId)
{
var rows = await _db.TrackAdditionLogs
.Where(l => l.SharedPlaylistId == sharedPlaylistId)
.Include(l => l.AddedByUser)
.OrderByDescending(l => l.AddedAtUtc)
.ToListAsync();
return rows
.GroupBy(l => l.TrackId)
.ToDictionary(g => g.Key, g => g.First().AddedByUser?.UserName);
}
}

View File

@@ -80,7 +80,7 @@ public class YandexApiService : IDisposable
/// </summary>
/// <param name="encryptedToken"></param>
/// <returns></returns>
public string DecryptToken(string encryptedToken)
public string? DecryptToken(string encryptedToken)
{
try
{

View File

@@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using PlaylistShared.Api.Data;
using PlaylistShared.Api.Entities;
using PlaylistShared.Shared.Yandex;
@@ -24,10 +24,10 @@ public class YandexAuthService
internal async Task<YandexAuthQr> GetQrOrGenerate(ApplicationUser user)
{
var existingSession = _dbContext.YandexAuthSessions
var existingSession = await _dbContext.YandexAuthSessions
.Where(s => s.UserId == user.Id && !s.IsConfirmed && s.CreatedAt > DateTime.UtcNow.AddMinutes(-5))
.OrderByDescending(s => s.CreatedAt)
.FirstOrDefault();
.FirstOrDefaultAsync();
if (existingSession != null)
{
@@ -45,14 +45,14 @@ public class YandexAuthService
{
var qr = await Api.Passport.GetAuthQRLinkAsync();
var trackId = Service.Client.AuthStorage.AuthToken.TrackId;
var csfrToken = Service.Client.AuthStorage.AuthToken.CsfrToken;
var csrfToken = Service.Client.AuthStorage.AuthToken.CsfrToken;
var headerProcessUuid = Service.Client.AuthStorage.HeaderToken.ProcessUuid;
var headerCsfrToken = Service.Client.AuthStorage.HeaderToken.CsfrToken;
var headerCsrfToken = Service.Client.AuthStorage.HeaderToken.CsfrToken;
if (string.IsNullOrEmpty(qr))
throw new Exception("Не удалось получить QR-ссылку");
var cookiesJson = SerializeCookies(_apiService.CookieContainer);
var cookiesJson = _apiService.EncryptToken(SerializeCookies(_apiService.CookieContainer));
var session = new YandexAuthSession
{
@@ -62,10 +62,9 @@ public class YandexAuthService
CreatedAt = DateTime.UtcNow,
IsConfirmed = false,
TrackId = trackId,
CsfrToken = csfrToken,
HeaderCsfrToken = headerCsfrToken,
CsrfToken = csrfToken,
HeaderCsrfToken = headerCsrfToken,
HeaderProcessId = headerProcessUuid,
};
_dbContext.YandexAuthSessions.Add(session);
@@ -83,16 +82,19 @@ public class YandexAuthService
var session = await _dbContext.YandexAuthSessions.FindAsync(sessionId);
if (session == null) return null;
RestoreCookies(Service.CookieContainer, session.SerializedCookies);
var decryptedCookies = _apiService.DecryptToken(session.SerializedCookies);
if (decryptedCookies == null) return null;
RestoreCookies(Service.CookieContainer, decryptedCookies);
if (Service.Client.AuthStorage.AuthToken is null)
{
Service.Client.AuthStorage.AuthToken = new();
}
Service.Client.AuthStorage.AuthToken.CsfrToken = session?.CsfrToken ?? "";
Service.Client.AuthStorage.AuthToken.TrackId = session?.TrackId ?? "";
Service.Client.AuthStorage.HeaderToken.CsfrToken = session?.HeaderCsfrToken ?? "";
Service.Client.AuthStorage.HeaderToken.ProcessUuid = session?.HeaderProcessId ?? "";
Service.Client.AuthStorage.AuthToken.CsfrToken = session.CsrfToken ?? "";
Service.Client.AuthStorage.AuthToken.TrackId = session.TrackId ?? "";
Service.Client.AuthStorage.HeaderToken.CsfrToken = session.HeaderCsrfToken ?? "";
Service.Client.AuthStorage.HeaderToken.ProcessUuid = session.HeaderProcessId ?? "";
var status = await Api.Passport.CheckQRStatusAsync();
@@ -100,36 +102,29 @@ public class YandexAuthService
{
try
{
var auth = await Api.Passport.AuthorizeByQRAsync();
await Api.Passport.AuthorizeByQRAsync();
}
catch (Exception ex)
catch
{
return new() { Status = Shared.Enums.YandexAuthQrStatus.Error, };
return new() { Status = Shared.Enums.YandexAuthQrStatus.Error };
}
_dbContext.YandexAuthSessions.Where(t => t.UserId == session.UserId).ExecuteDelete();
_dbContext.SaveChanges();
await _dbContext.YandexAuthSessions
.Where(t => t.UserId == session.UserId)
.ExecuteDeleteAsync();
await _dbContext.SaveChangesAsync();
return new() { Status = Shared.Enums.YandexAuthQrStatus.Authorized, };
return new() { Status = Shared.Enums.YandexAuthQrStatus.Authorized };
}
return new()
{
Status = Shared.Enums.YandexAuthQrStatus.Pending,
};
return new() { Status = Shared.Enums.YandexAuthQrStatus.Pending };
}
private string SerializeCookies(CookieContainer container)
{
var allCookies = new List<object>();
var cookies = container.GetAllCookies();
foreach (Cookie cookie in cookies)
{
foreach (Cookie cookie in container.GetAllCookies())
allCookies.Add(new { cookie.Name, cookie.Value, cookie.Domain, cookie.Path });
}
return JsonSerializer.Serialize(allCookies);
}
@@ -137,9 +132,7 @@ public class YandexAuthService
{
var cookies = JsonSerializer.Deserialize<List<CookieData>>(serializedCookies);
foreach (var c in cookies)
{
container.Add(new Cookie(c.Name, c.Value, c.Path, c.Domain));
}
}
private class CookieData
@@ -149,4 +142,4 @@ public class YandexAuthService
public string Domain { get; set; }
public string Path { get; set; }
}
}
}

View File

@@ -35,6 +35,21 @@ public class YandexMusicService
return await Api.Playlist.GetAsync(ownerUid, kind);
}
public async Task<YandexPlaylistData?> GetPlaylistDataAsync(ApplicationUser user, string ownerUid, string kind)
{
var playlist = await GetPlaylistAsync(user, ownerUid, kind);
return playlist == null ? null : MapToPlaylistData(playlist);
}
public async Task<(List<YPlaylist>? OwnPlaylists, string? AccountUid)> GetOwnFavoritesAsync(ApplicationUser user)
{
await AuthorizeIfNot(user);
var favorites = await Api.Playlist.FavoritesAsync();
var accountUid = _yandexApiService.Client.Account?.Uid;
var ownPlaylists = favorites?.Where(p => p.Owner?.Uid == accountUid).ToList();
return (ownPlaylists, accountUid);
}
public async Task<YPlaylist?> CreatePlaylistAsync(ApplicationUser user, string title)
{
await AuthorizeIfNot(user);
@@ -339,4 +354,24 @@ public class YandexMusicService
return result;
}
private static YandexPlaylistData MapToPlaylistData(YPlaylist playlist) => new()
{
Title = playlist.Title,
Description = playlist.Description,
Tracks = playlist.Tracks.Select(t => new YandexTrack
{
TrackId = t.Track.Id,
Title = t.Track.Title,
Artists = t.Track.Artists.Select(a => new YandexArtist
{
Id = a.Id,
Name = a.Name,
CoverUrl = a.Cover.GetUrl(),
Description = a.Description?.Text ?? string.Empty,
}).ToList(),
DurationMs = (int)(t.Track?.DurationMs ?? 0),
CoverUri = t.Track?.CoverUri ?? ""
}).ToList()
};
}