Доработана QR авторизация
This commit is contained in:
@@ -64,7 +64,7 @@ public class YandexAccountController : ControllerBase
|
||||
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var qr = await _yandexService.GenerateQrAsync(user);
|
||||
var qr = await _yandexService.GetQrOrGenerate(user);
|
||||
|
||||
return Ok(ApiResponse<YandexAuthQr>.Ok(qr));
|
||||
}
|
||||
|
||||
@@ -131,6 +131,12 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
||||
entity.Property(e => e.CsfrToken)
|
||||
.HasMaxLength(200)
|
||||
.IsRequired(false);
|
||||
entity.Property(e => e.HeaderCsfrToken)
|
||||
.HasMaxLength(200)
|
||||
.IsRequired(false);
|
||||
entity.Property(e => e.HeaderProcessId)
|
||||
.HasMaxLength(200)
|
||||
.IsRequired(false);
|
||||
entity.HasIndex(e => e.UserId)
|
||||
.HasDatabaseName("IX_YandexAuthSessions_UserId");
|
||||
});
|
||||
|
||||
676
PlaylistShared.Api/Data/Migrations/20260420123450_AddYandexAuthSessions_Header.Designer.cs
generated
Normal file
676
PlaylistShared.Api/Data/Migrations/20260420123450_AddYandexAuthSessions_Header.Designer.cs
generated
Normal file
@@ -0,0 +1,676 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using PlaylistShared.Api.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PlaylistShared.Api.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260420123450_AddYandexAuthSessions_Header")]
|
||||
partial class AddYandexAuthSessions_Header
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Xml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataProtectionKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("RefreshTokenExpiryUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("YandexAccessToken")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("YandexId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("YandexRefreshToken")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("YandexTokenExpiryUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("SharedPlaylistId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("AddedAtUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("UserId", "SharedPlaylistId");
|
||||
|
||||
b.HasIndex("SharedPlaylistId");
|
||||
|
||||
b.ToTable("FavoritePlaylists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("AddPermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid>("CreatorUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("PlayPermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RemovePermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ShareToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("nvarchar(255)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("ViewPermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("YandexPlaylistKind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("YandexPlaylistOwnerUid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("YandexPlaylistUuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorUserId");
|
||||
|
||||
b.HasIndex("ShareToken")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SharedPlaylists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.TrackAdditionLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("AddedAtUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("AddedByUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(449)");
|
||||
|
||||
b.Property<Guid>("SharedPlaylistId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("TrackId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AddedByUserId");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.HasIndex("SharedPlaylistId", "TrackId");
|
||||
|
||||
b.ToTable("TrackAdditionLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||
{
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(449)
|
||||
.HasColumnType("nvarchar(449)");
|
||||
|
||||
b.Property<Guid?>("AssociatedUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ClientIpAddress")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("FirstSeenUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("LastSeenUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("SessionId");
|
||||
|
||||
b.HasIndex("AssociatedUserId");
|
||||
|
||||
b.ToTable("UserSessions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.YandexAuthSession", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("ConfirmedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CsfrToken")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("HeaderCsfrToken")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("HeaderProcessId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<bool>("IsConfirmed")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<string>("QrCodeUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("SerializedCookies")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TrackId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("IX_YandexAuthSessions_UserId");
|
||||
|
||||
b.ToTable("YandexAuthSessions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrackRemovalLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("RemovedAtUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("RemovedByUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(449)");
|
||||
|
||||
b.Property<Guid>("SharedPlaylistId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("TrackId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RemovedByUserId");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.HasIndex("SharedPlaylistId", "TrackId");
|
||||
|
||||
b.ToTable("TrackRemovalLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||
.WithMany()
|
||||
.HasForeignKey("SharedPlaylistId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||
.WithMany("FavoritePlaylists")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SharedPlaylist");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "Creator")
|
||||
.WithMany("OwnedPlaylists")
|
||||
.HasForeignKey("CreatorUserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.TrackAdditionLog", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "AddedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("AddedByUserId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.UserSession", "Session")
|
||||
.WithMany("TrackAdditionLogs")
|
||||
.HasForeignKey("SessionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||
.WithMany("TrackAdditionLogs")
|
||||
.HasForeignKey("SharedPlaylistId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AddedByUser");
|
||||
|
||||
b.Navigation("Session");
|
||||
|
||||
b.Navigation("SharedPlaylist");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("AssociatedUserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.YandexAuthSession", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrackRemovalLog", b =>
|
||||
{
|
||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "RemovedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RemovedByUserId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.UserSession", "Session")
|
||||
.WithMany("TrackRemovalLogs")
|
||||
.HasForeignKey("SessionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||
.WithMany()
|
||||
.HasForeignKey("SharedPlaylistId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RemovedByUser");
|
||||
|
||||
b.Navigation("Session");
|
||||
|
||||
b.Navigation("SharedPlaylist");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("FavoritePlaylists");
|
||||
|
||||
b.Navigation("OwnedPlaylists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||
{
|
||||
b.Navigation("TrackAdditionLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||
{
|
||||
b.Navigation("TrackAdditionLogs");
|
||||
|
||||
b.Navigation("TrackRemovalLogs");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PlaylistShared.Api.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddYandexAuthSessions_Header : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HeaderCsfrToken",
|
||||
table: "YandexAuthSessions",
|
||||
type: "nvarchar(200)",
|
||||
maxLength: 200,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HeaderProcessId",
|
||||
table: "YandexAuthSessions",
|
||||
type: "nvarchar(200)",
|
||||
maxLength: 200,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HeaderCsfrToken",
|
||||
table: "YandexAuthSessions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HeaderProcessId",
|
||||
table: "YandexAuthSessions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,6 +425,14 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("HeaderCsfrToken")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("HeaderProcessId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<bool>("IsConfirmed")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
||||
@@ -11,6 +11,8 @@ public class YandexAuthSession
|
||||
public bool IsConfirmed { get; set; }
|
||||
public string? TrackId { get; set; }
|
||||
public string? CsfrToken { get; set; }
|
||||
public string? HeaderProcessId { get; set; }
|
||||
public string? HeaderCsfrToken { get; set; }
|
||||
|
||||
public ApplicationUser? User { get; set; }
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
|
||||
<PackageReference Include="YandexMusic" Version="0.0.11" />
|
||||
<PackageReference Include="YandexMusic" Version="0.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using PlaylistShared.Api.Entities;
|
||||
using System.Net;
|
||||
using YandexMusic;
|
||||
using YandexMusic.API.Common;
|
||||
|
||||
namespace PlaylistShared.Api.Services;
|
||||
|
||||
@@ -12,7 +11,6 @@ namespace PlaylistShared.Api.Services;
|
||||
public class YandexApiService : IDisposable
|
||||
{
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly YandexMusicClient _client;
|
||||
private readonly CookieContainer _cookieContainer;
|
||||
|
||||
@@ -33,12 +31,7 @@ public class YandexApiService : IDisposable
|
||||
{
|
||||
_dataProtector = provider.CreateProtector("YandexTokens");
|
||||
_cookieContainer = new();
|
||||
_httpClient = YandexMusicHttpClientFactory.CreateDefault(
|
||||
cookieContainer: _cookieContainer,
|
||||
proxy: proxy,
|
||||
timeout: timeout
|
||||
);
|
||||
_client = new YandexMusicClient(_httpClient);
|
||||
_client = new YandexMusicClient(_cookieContainer, proxy, timeout);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,21 +100,6 @@ public class YandexApiService : IDisposable
|
||||
return cookie?.Value;
|
||||
}
|
||||
|
||||
private void UpdateHttpClientCookieContainer(CookieContainer container)
|
||||
{
|
||||
var handler = GetInnerHandler(_httpClient);
|
||||
if (handler is HttpClientHandler httpHandler)
|
||||
httpHandler.CookieContainer = container;
|
||||
}
|
||||
|
||||
private static HttpMessageHandler GetInnerHandler(HttpClient client)
|
||||
{
|
||||
var field = client.GetType().GetField("_handler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (field?.GetValue(client) is HttpMessageHandler handler)
|
||||
return handler;
|
||||
return new HttpClientHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизуется с помощью OAuth-токена.
|
||||
/// </summary>
|
||||
@@ -133,6 +111,5 @@ public class YandexApiService : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using PlaylistShared.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PlaylistShared.Api.Data;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Shared.Yandex;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using YandexMusic.API.Models.Account;
|
||||
|
||||
namespace PlaylistShared.Api.Services;
|
||||
|
||||
@@ -20,12 +20,33 @@ public class YandexAuthService
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
internal async Task<YandexAuthQr> GetQrOrGenerate(ApplicationUser user)
|
||||
{
|
||||
var existingSession = _dbContext.YandexAuthSessions
|
||||
.Where(s => s.UserId == user.Id && !s.IsConfirmed && s.CreatedAt > DateTime.UtcNow.AddMinutes(-5))
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (existingSession != null)
|
||||
{
|
||||
return new YandexAuthQr
|
||||
{
|
||||
QrLink = existingSession.QrCodeUrl,
|
||||
SessionId = existingSession.Id.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
return await GenerateQrAsync(user);
|
||||
}
|
||||
|
||||
internal async Task<YandexAuthQr> GenerateQrAsync(ApplicationUser user)
|
||||
{
|
||||
var client = _apiService.Client;
|
||||
var qr = await client.GetAuthQRLink();
|
||||
var trackId = client.AuthStorage.AuthToken.TrackId;
|
||||
var csfrToken = client.AuthStorage.AuthToken.CsfrToken;
|
||||
var headerProcessUuid = client.AuthStorage.HeaderToken.ProcessUuid;
|
||||
var headerCsfrToken = client.AuthStorage.HeaderToken.CsfrToken;
|
||||
|
||||
if (string.IsNullOrEmpty(qr))
|
||||
throw new Exception("Не удалось получить QR-ссылку");
|
||||
@@ -41,6 +62,8 @@ public class YandexAuthService
|
||||
IsConfirmed = false,
|
||||
TrackId = trackId,
|
||||
CsfrToken = csfrToken,
|
||||
HeaderCsfrToken = headerCsfrToken,
|
||||
HeaderProcessId = headerProcessUuid,
|
||||
|
||||
};
|
||||
|
||||
@@ -67,19 +90,26 @@ public class YandexAuthService
|
||||
|
||||
_apiService.Client.AuthStorage.AuthToken.CsfrToken = session?.CsfrToken ?? "";
|
||||
_apiService.Client.AuthStorage.AuthToken.TrackId = session?.TrackId ?? "";
|
||||
_apiService.Client.AuthStorage.HeaderToken.CsfrToken = session?.HeaderCsfrToken ?? "";
|
||||
_apiService.Client.AuthStorage.HeaderToken.ProcessUuid = session?.HeaderProcessId ?? "";
|
||||
|
||||
var status = await _apiService.Client.AuthorizeByQR();
|
||||
var status = await _apiService.Client.CheckQRStatusAsync();
|
||||
|
||||
if (status?.Status == YAuthStatus.Ok)
|
||||
if (status?.State == "otp_auth_finished")
|
||||
{
|
||||
session.ConfirmedAt = DateTime.UtcNow;
|
||||
session.IsConfirmed = true;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
return new()
|
||||
try
|
||||
{
|
||||
Status = Shared.Enums.YandexAuthQrStatus.Authorized,
|
||||
};
|
||||
var auth = await _apiService.Client.AuthorizeByQR();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new() { Status = Shared.Enums.YandexAuthQrStatus.Error, };
|
||||
}
|
||||
|
||||
_dbContext.YandexAuthSessions.Where(t => t.UserId == session.UserId).ExecuteDelete();
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
return new() { Status = Shared.Enums.YandexAuthQrStatus.Authorized, };
|
||||
}
|
||||
|
||||
return new()
|
||||
@@ -91,7 +121,6 @@ public class YandexAuthService
|
||||
|
||||
private string SerializeCookies(CookieContainer container)
|
||||
{
|
||||
var domains = new[] { "yandex.ru", "passport.yandex.ru", ".yandex.ru" };
|
||||
var allCookies = new List<object>();
|
||||
|
||||
var cookies = container.GetAllCookies();
|
||||
@@ -108,8 +137,7 @@ public class YandexAuthService
|
||||
var cookies = JsonSerializer.Deserialize<List<CookieData>>(serializedCookies);
|
||||
foreach (var c in cookies)
|
||||
{
|
||||
var uri = new Uri($"{c.Domain}");
|
||||
container.Add(uri, new Cookie(c.Name, c.Value, c.Path, c.Domain));
|
||||
container.Add(new Cookie(c.Name, c.Value, c.Path, c.Domain));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(2000, token);
|
||||
await Task.Delay(500, token);
|
||||
var statusResponse = await Http.GetFromJsonAsync<ApiResponse<YandexAuthQrCheck>>($"/api/yandexaccount/qr/{_sessionId}", token);
|
||||
if (statusResponse?.Data != null)
|
||||
{
|
||||
@@ -101,13 +101,15 @@
|
||||
_statusText = "Авторизация успешна!";
|
||||
_isWaiting = false;
|
||||
Snackbar.Add("Авторизация выполнена", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
_cts?.Cancel();
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
return;
|
||||
case Shared.Enums.YandexAuthQrStatus.Expired:
|
||||
_cts?.Cancel();
|
||||
ShowError("Срок действия QR-кода истёк");
|
||||
return;
|
||||
case Shared.Enums.YandexAuthQrStatus.Error:
|
||||
_cts?.Cancel();
|
||||
ShowError("Ошибка авторизации");
|
||||
return;
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
Dense
|
||||
Color="Color.Primary">
|
||||
<MudMenuItem OnClick="OpenTokenDialog">Token</MudMenuItem>
|
||||
<MudMenuItem OnClick="OpenQrDialog">Qr</MudMenuItem>
|
||||
<MudMenuItem OnClick="OpenQrServerDialog">QR</MudMenuItem>
|
||||
</MudMenu>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
@@ -77,10 +77,10 @@
|
||||
if (!result.Canceled) await LoadStatus();
|
||||
}
|
||||
|
||||
private async Task OpenQrDialog()
|
||||
private async Task OpenQrServerDialog()
|
||||
{
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
var dialog = await DialogService.ShowAsync<YandexQrDialog>("", options);
|
||||
var dialog = await DialogService.ShowAsync<YandexQrServerDialog>("", options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled) await LoadStatus();
|
||||
|
||||
Reference in New Issue
Block a user