auth changes
This commit is contained in:
@@ -22,8 +22,8 @@ namespace WatchIt.Database.Model.Account
|
|||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public short GenderId { get; set; }
|
public short? GenderId { get; set; }
|
||||||
public Guid? ProfilePictureId { get; set; }
|
public Guid? ProfilePictureId { get; set; }
|
||||||
public Guid? BackgroundPictureId { get; set; }
|
public Guid? BackgroundPictureId { get; set; }
|
||||||
public byte[] Password { get; set; }
|
public byte[] Password { get; set; }
|
||||||
@@ -49,6 +49,8 @@ namespace WatchIt.Database.Model.Account
|
|||||||
public IEnumerable<RatingMediaSeriesSeason> RatingMediaSeriesSeason { get; set; }
|
public IEnumerable<RatingMediaSeriesSeason> RatingMediaSeriesSeason { get; set; }
|
||||||
public IEnumerable<RatingMediaSeriesEpisode> RatingMediaSeriesEpisode { get; set; }
|
public IEnumerable<RatingMediaSeriesEpisode> RatingMediaSeriesEpisode { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<AccountRefreshToken> AccountRefreshTokens { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -76,10 +78,8 @@ namespace WatchIt.Database.Model.Account
|
|||||||
|
|
||||||
builder.HasOne(x => x.Gender)
|
builder.HasOne(x => x.Gender)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(x => x.GenderId)
|
.HasForeignKey(x => x.GenderId);
|
||||||
.IsRequired();
|
builder.Property(x => x.GenderId);
|
||||||
builder.Property(x => x.GenderId)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
builder.HasOne(x => x.ProfilePicture)
|
builder.HasOne(x => x.ProfilePicture)
|
||||||
.WithOne(x => x.Account)
|
.WithOne(x => x.Account)
|
||||||
@@ -104,7 +104,8 @@ namespace WatchIt.Database.Model.Account
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.Property(x => x.IsAdmin)
|
builder.Property(x => x.IsAdmin)
|
||||||
.IsRequired();
|
.IsRequired()
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
builder.Property(x => x.CreationDate)
|
builder.Property(x => x.CreationDate)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.Database.Model.Account
|
||||||
|
{
|
||||||
|
public class AccountRefreshToken : IEntity<AccountRefreshToken>
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
public DateTime ExpirationDate { get; set; }
|
||||||
|
public bool IsExtendable { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region NAVIGATION
|
||||||
|
|
||||||
|
public Account Account { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
static void IEntity<AccountRefreshToken>.Build(EntityTypeBuilder<AccountRefreshToken> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(x => x.Id);
|
||||||
|
builder.HasIndex(x => x.Id)
|
||||||
|
.IsUnique();
|
||||||
|
builder.Property(x => x.Id)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(x => x.Account)
|
||||||
|
.WithMany(x => x.AccountRefreshTokens)
|
||||||
|
.HasForeignKey(x => x.AccountId)
|
||||||
|
.IsRequired();
|
||||||
|
builder.Property(x => x.AccountId)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(x => x.ExpirationDate)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(x => x.IsExtendable)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ namespace WatchIt.Database
|
|||||||
// Account
|
// Account
|
||||||
public virtual DbSet<Account> Accounts { get; set; }
|
public virtual DbSet<Account> Accounts { get; set; }
|
||||||
public virtual DbSet<AccountProfilePicture> AccountProfilePictures { get; set; }
|
public virtual DbSet<AccountProfilePicture> AccountProfilePictures { get; set; }
|
||||||
|
public virtual DbSet<AccountRefreshToken> AccountRefreshTokens { get; set; }
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
public virtual DbSet<Media> Media { get; set; }
|
public virtual DbSet<Media> Media { get; set; }
|
||||||
@@ -87,6 +88,7 @@ namespace WatchIt.Database
|
|||||||
// Account
|
// Account
|
||||||
EntityBuilder.Build<Account>(modelBuilder);
|
EntityBuilder.Build<Account>(modelBuilder);
|
||||||
EntityBuilder.Build<AccountProfilePicture>(modelBuilder);
|
EntityBuilder.Build<AccountProfilePicture>(modelBuilder);
|
||||||
|
EntityBuilder.Build<AccountRefreshToken>(modelBuilder);
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
EntityBuilder.Build<Media>(modelBuilder);
|
EntityBuilder.Build<Media>(modelBuilder);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace WatchIt.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class _0003_GenderNotRequiredAndDefaultNotAdmin : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Accounts_Genders_GenderId",
|
||||||
|
table: "Accounts");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<bool>(
|
||||||
|
name: "IsAdmin",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false,
|
||||||
|
oldClrType: typeof(bool),
|
||||||
|
oldType: "boolean");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<short>(
|
||||||
|
name: "GenderId",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "smallint",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(short),
|
||||||
|
oldType: "smallint");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Accounts_Genders_GenderId",
|
||||||
|
table: "Accounts",
|
||||||
|
column: "GenderId",
|
||||||
|
principalTable: "Genders",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Accounts_Genders_GenderId",
|
||||||
|
table: "Accounts");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<bool>(
|
||||||
|
name: "IsAdmin",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(bool),
|
||||||
|
oldType: "boolean",
|
||||||
|
oldDefaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<short>(
|
||||||
|
name: "GenderId",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "smallint",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: (short)0,
|
||||||
|
oldClrType: typeof(short),
|
||||||
|
oldType: "smallint",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Accounts_Genders_GenderId",
|
||||||
|
table: "Accounts",
|
||||||
|
column: "GenderId",
|
||||||
|
principalTable: "Genders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1296
WatchIt.Database/WatchIt.Database/Migrations/20240324144605_0004_AccountDescriptionNullable.Designer.cs
generated
Normal file
1296
WatchIt.Database/WatchIt.Database/Migrations/20240324144605_0004_AccountDescriptionNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace WatchIt.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class _0004_AccountDescriptionNullable : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Description",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "character varying(1000)",
|
||||||
|
maxLength: 1000,
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(1000)",
|
||||||
|
oldMaxLength: 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Description",
|
||||||
|
table: "Accounts",
|
||||||
|
type: "character varying(1000)",
|
||||||
|
maxLength: 1000,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "",
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(1000)",
|
||||||
|
oldMaxLength: 1000,
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1331
WatchIt.Database/WatchIt.Database/Migrations/20240324195235_0005_AccountRefreshTokensAdded.Designer.cs
generated
Normal file
1331
WatchIt.Database/WatchIt.Database/Migrations/20240324195235_0005_AccountRefreshTokensAdded.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace WatchIt.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class _0005_AccountRefreshTokensAdded : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AccountRefreshTokens",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
AccountId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Lifetime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AccountRefreshTokens", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AccountRefreshTokens_Accounts_AccountId",
|
||||||
|
column: x => x.AccountId,
|
||||||
|
principalTable: "Accounts",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AccountRefreshTokens_AccountId",
|
||||||
|
table: "AccountRefreshTokens",
|
||||||
|
column: "AccountId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AccountRefreshTokens_Id",
|
||||||
|
table: "AccountRefreshTokens",
|
||||||
|
column: "Id",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AccountRefreshTokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1334
WatchIt.Database/WatchIt.Database/Migrations/20240324205952_0006_AccountRefreshTokenChanges.Designer.cs
generated
Normal file
1334
WatchIt.Database/WatchIt.Database/Migrations/20240324205952_0006_AccountRefreshTokenChanges.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace WatchIt.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class _0006_AccountRefreshTokenChanges : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "Lifetime",
|
||||||
|
table: "AccountRefreshTokens",
|
||||||
|
newName: "ExpirationDate");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsExtendable",
|
||||||
|
table: "AccountRefreshTokens",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsExtendable",
|
||||||
|
table: "AccountRefreshTokens");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "ExpirationDate",
|
||||||
|
table: "AccountRefreshTokens",
|
||||||
|
newName: "Lifetime");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ namespace WatchIt.Database.Migrations
|
|||||||
.HasDefaultValueSql("now()");
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(1000)
|
.HasMaxLength(1000)
|
||||||
.HasColumnType("character varying(1000)");
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
@@ -48,11 +47,13 @@ namespace WatchIt.Database.Migrations
|
|||||||
.HasMaxLength(320)
|
.HasMaxLength(320)
|
||||||
.HasColumnType("character varying(320)");
|
.HasColumnType("character varying(320)");
|
||||||
|
|
||||||
b.Property<short>("GenderId")
|
b.Property<short?>("GenderId")
|
||||||
.HasColumnType("smallint");
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
b.Property<bool>("IsAdmin")
|
b.Property<bool>("IsAdmin")
|
||||||
.HasColumnType("boolean");
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
b.Property<DateTime>("LastActive")
|
b.Property<DateTime>("LastActive")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -126,6 +127,31 @@ namespace WatchIt.Database.Migrations
|
|||||||
b.ToTable("AccountProfilePictures");
|
b.ToTable("AccountProfilePictures");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("WatchIt.Database.Model.Account.AccountRefreshToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<long>("AccountId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpirationDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("IsExtendable")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId");
|
||||||
|
|
||||||
|
b.HasIndex("Id")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("AccountRefreshTokens");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("WatchIt.Database.Model.Common.Country", b =>
|
modelBuilder.Entity("WatchIt.Database.Model.Common.Country", b =>
|
||||||
{
|
{
|
||||||
b.Property<short>("Id")
|
b.Property<short>("Id")
|
||||||
@@ -891,9 +917,7 @@ namespace WatchIt.Database.Migrations
|
|||||||
|
|
||||||
b.HasOne("WatchIt.Database.Model.Common.Gender", "Gender")
|
b.HasOne("WatchIt.Database.Model.Common.Gender", "Gender")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("GenderId")
|
.HasForeignKey("GenderId");
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("WatchIt.Database.Model.Account.AccountProfilePicture", "ProfilePicture")
|
b.HasOne("WatchIt.Database.Model.Account.AccountProfilePicture", "ProfilePicture")
|
||||||
.WithOne("Account")
|
.WithOne("Account")
|
||||||
@@ -906,6 +930,17 @@ namespace WatchIt.Database.Migrations
|
|||||||
b.Navigation("ProfilePicture");
|
b.Navigation("ProfilePicture");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("WatchIt.Database.Model.Account.AccountRefreshToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("WatchIt.Database.Model.Account.Account", "Account")
|
||||||
|
.WithMany("AccountRefreshTokens")
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("WatchIt.Database.Model.Media.Media", b =>
|
modelBuilder.Entity("WatchIt.Database.Model.Media.Media", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("WatchIt.Database.Model.Media.MediaMovie", null)
|
b.HasOne("WatchIt.Database.Model.Media.MediaMovie", null)
|
||||||
@@ -1188,6 +1223,8 @@ namespace WatchIt.Database.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("WatchIt.Database.Model.Account.Account", b =>
|
modelBuilder.Entity("WatchIt.Database.Model.Account.Account", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("AccountRefreshTokens");
|
||||||
|
|
||||||
b.Navigation("RatingMedia");
|
b.Navigation("RatingMedia");
|
||||||
|
|
||||||
b.Navigation("RatingMediaSeriesEpisode");
|
b.Navigation("RatingMediaSeriesEpisode");
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.Shared.Models.Accounts.Authenticate
|
||||||
|
{
|
||||||
|
public class AuthenticateRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("username_or_email")]
|
||||||
|
public string UsernameOrEmail { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("remember_me")]
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.Shared.Models.Accounts.Authenticate
|
||||||
|
{
|
||||||
|
public class AuthenticateResponse
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("access_token")]
|
||||||
|
public required string AccessToken { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("refresh_token")]
|
||||||
|
public required string RefreshToken { get; init; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.Shared.Models.Accounts.Register
|
||||||
|
{
|
||||||
|
public class RegisterRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.Database.Model.Account;
|
||||||
|
|
||||||
|
namespace WatchIt.Shared.Models.Accounts.Register
|
||||||
|
{
|
||||||
|
public class RegisterResponse
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public required long Id { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("username")]
|
||||||
|
public required string Username { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public required string Email { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("creation_date")]
|
||||||
|
public required DateTime CreationDate { get; init; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONVERTION
|
||||||
|
|
||||||
|
public static implicit operator RegisterResponse(Account account) => new RegisterResponse
|
||||||
|
{
|
||||||
|
Id = account.Id,
|
||||||
|
Username = account.Username,
|
||||||
|
Email = account.Email,
|
||||||
|
CreationDate = account.CreationDate
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
107
WatchIt.Shared/WatchIt.Shared.Models/RequestResult.cs
Normal file
107
WatchIt.Shared/WatchIt.Shared.Models/RequestResult.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
namespace WatchIt.Shared.Models
|
||||||
|
{
|
||||||
|
public class RequestResult
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public RequestResultStatus Status { get; }
|
||||||
|
public IEnumerable<string> ValidationMessages { get; init; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
internal RequestResult(RequestResultStatus status) => Status = status;
|
||||||
|
|
||||||
|
public static RequestResult Ok() => new RequestResult(RequestResultStatus.Ok);
|
||||||
|
public static RequestResult<T> Ok<T>() => new RequestResult<T>(RequestResultStatus.Ok);
|
||||||
|
public static RequestResult<T> Ok<T>(T data) => new RequestResult<T>(RequestResultStatus.Ok) { Data = data };
|
||||||
|
public static RequestResult<T> Created<T>(string location, T resource) => new RequestResult<T>(RequestResultStatus.Created) { NewResourceLocation = location, Data = resource };
|
||||||
|
public static RequestResult NoContent() => new RequestResult(RequestResultStatus.NoContent);
|
||||||
|
public static RequestResult<T> NoContent<T>() => new RequestResult<T>(RequestResultStatus.NoContent);
|
||||||
|
public static RequestResult BadRequest(params string[] validationErrors) => new RequestResult(RequestResultStatus.BadRequest) { ValidationMessages = validationErrors };
|
||||||
|
public static RequestResult<T> BadRequest<T>(params string[] validationErrors) => new RequestResult<T>(RequestResultStatus.BadRequest) { ValidationMessages = validationErrors };
|
||||||
|
public static RequestResult Unauthorized(params string[] validationErrors) => new RequestResult(RequestResultStatus.Unauthorized) { ValidationMessages = validationErrors };
|
||||||
|
public static RequestResult<T> Unauthorized<T>(params string[] validationErrors) => new RequestResult<T>(RequestResultStatus.Unauthorized) { ValidationMessages = validationErrors };
|
||||||
|
public static RequestResult Forbidden() => new RequestResult(RequestResultStatus.Forbidden);
|
||||||
|
public static RequestResult<T> Forbidden<T>() => new RequestResult<T>(RequestResultStatus.Forbidden);
|
||||||
|
public static RequestResult NotFound() => new RequestResult(RequestResultStatus.NotFound);
|
||||||
|
public static RequestResult<T> NotFound<T>() => new RequestResult<T>(RequestResultStatus.NotFound);
|
||||||
|
public static RequestResult Conflict() => new RequestResult(RequestResultStatus.Conflict);
|
||||||
|
public static RequestResult<T> Conflict<T>() => new RequestResult<T>(RequestResultStatus.Conflict);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONVERSION
|
||||||
|
|
||||||
|
public static implicit operator ActionResult(RequestResult result) => result.Status switch
|
||||||
|
{
|
||||||
|
RequestResultStatus.Ok => HandleOk(result),
|
||||||
|
RequestResultStatus.NoContent => HandleNoContent(),
|
||||||
|
RequestResultStatus.BadRequest => HandleBadRequest(result),
|
||||||
|
RequestResultStatus.Unauthorized => HandleUnauthorized(result),
|
||||||
|
RequestResultStatus.Forbidden => HandleForbidden(),
|
||||||
|
RequestResultStatus.NotFound => HandleNotFound(),
|
||||||
|
RequestResultStatus.Conflict => HandleConflict(),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected static ActionResult HandleOk(RequestResult result) => new OkResult();
|
||||||
|
protected static ActionResult HandleNoContent() => new NoContentResult();
|
||||||
|
protected static ActionResult HandleBadRequest(RequestResult result) => new BadRequestObjectResult(result.ValidationMessages);
|
||||||
|
protected static ActionResult HandleUnauthorized(RequestResult result) => new UnauthorizedObjectResult(result.ValidationMessages);
|
||||||
|
protected static ActionResult HandleForbidden() => new ForbidResult();
|
||||||
|
protected static ActionResult HandleNotFound() => new NotFoundResult();
|
||||||
|
protected static ActionResult HandleConflict() => new ConflictResult();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RequestResult<T> : RequestResult
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public T? Data { get; init; }
|
||||||
|
public string? NewResourceLocation { get; init; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
internal RequestResult(RequestResultStatus type) : base(type) { }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONVERSION
|
||||||
|
|
||||||
|
public static implicit operator ActionResult(RequestResult<T> result) => result.Status switch
|
||||||
|
{
|
||||||
|
RequestResultStatus.Ok => HandleOk(result),
|
||||||
|
RequestResultStatus.Created => HandleCreated(result),
|
||||||
|
RequestResultStatus.NoContent => HandleNoContent(),
|
||||||
|
RequestResultStatus.BadRequest => HandleBadRequest(result),
|
||||||
|
RequestResultStatus.Unauthorized => HandleUnauthorized(result),
|
||||||
|
RequestResultStatus.Forbidden => HandleForbidden(),
|
||||||
|
RequestResultStatus.NotFound => HandleNotFound(),
|
||||||
|
RequestResultStatus.Conflict => HandleConflict(),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ActionResult HandleOk(RequestResult<T> result) => result.Data is null ? new OkResult() : new OkObjectResult(result.Data);
|
||||||
|
private static ActionResult HandleCreated(RequestResult<T> result) => new CreatedResult(result.NewResourceLocation, result.Data);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
20
WatchIt.Shared/WatchIt.Shared.Models/RequestResultStatus.cs
Normal file
20
WatchIt.Shared/WatchIt.Shared.Models/RequestResultStatus.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.Shared.Models
|
||||||
|
{
|
||||||
|
public enum RequestResultStatus
|
||||||
|
{
|
||||||
|
Ok = 200,
|
||||||
|
Created = 201,
|
||||||
|
NoContent = 204,
|
||||||
|
BadRequest = 400,
|
||||||
|
Unauthorized = 401,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
Conflict = 409,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Authenticate;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Register;
|
||||||
|
using WatchIt.WebAPI.Services.Controllers;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/accounts")]
|
||||||
|
public class AccountsController(IAccountsControllerService accountsControllerService) : ControllerBase
|
||||||
|
{
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("register")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ProducesResponseType(typeof(RegisterResponse), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
public async Task<ActionResult> Register([FromBody] RegisterRequest data) => await accountsControllerService.Register(data);
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("authenticate")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> Authenticate([FromBody] AuthenticateRequest data) => await accountsControllerService.Authenticate(data);
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("authenticate-refresh")]
|
||||||
|
[Authorize(AuthenticationSchemes = "refresh")]
|
||||||
|
[ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> AuthenticateRefresh() => await accountsControllerService.AuthenticateRefresh();
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SimpleToolkit.Extensions;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using WatchIt.Database;
|
||||||
|
using WatchIt.Database.Model.Account;
|
||||||
|
using WatchIt.Shared.Models;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Authenticate;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Register;
|
||||||
|
using WatchIt.WebAPI.Services.Utility.JWT;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Controllers
|
||||||
|
{
|
||||||
|
public interface IAccountsControllerService
|
||||||
|
{
|
||||||
|
Task<RequestResult<RegisterResponse>> Register(RegisterRequest data);
|
||||||
|
Task<RequestResult<AuthenticateResponse>> Authenticate(AuthenticateRequest data);
|
||||||
|
Task<RequestResult<AuthenticateResponse>> AuthenticateRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AccountsControllerService(IJWTService jwtService, DatabaseContext database) : IAccountsControllerService
|
||||||
|
{
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task<RequestResult<RegisterResponse>> Register(RegisterRequest data)
|
||||||
|
{
|
||||||
|
string leftSalt = StringExtensions.CreateRandom(20);
|
||||||
|
string rightSalt = StringExtensions.CreateRandom(20);
|
||||||
|
byte[] hash = ComputeHash(data.Password, leftSalt, rightSalt);
|
||||||
|
|
||||||
|
Account account = new Account
|
||||||
|
{
|
||||||
|
Username = data.Username,
|
||||||
|
Email = data.Email,
|
||||||
|
Password = hash,
|
||||||
|
LeftSalt = leftSalt,
|
||||||
|
RightSalt = rightSalt
|
||||||
|
};
|
||||||
|
await database.Accounts.AddAsync(account);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return RequestResult.Created<RegisterResponse>($"accounts/{account.Id}", account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult<AuthenticateResponse>> Authenticate(AuthenticateRequest data)
|
||||||
|
{
|
||||||
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => string.Equals(x.Email, data.UsernameOrEmail) || string.Equals(x.Username, data.UsernameOrEmail));
|
||||||
|
if (account is null)
|
||||||
|
{
|
||||||
|
return RequestResult.Unauthorized<AuthenticateResponse>("User does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] hash = ComputeHash(data.Password, account.LeftSalt, account.RightSalt);
|
||||||
|
if (!Enumerable.SequenceEqual(hash, account.Password))
|
||||||
|
{
|
||||||
|
return RequestResult.Unauthorized<AuthenticateResponse>("Incorrect password");
|
||||||
|
}
|
||||||
|
|
||||||
|
Task<string> refreshTokenTask = jwtService.CreateRefreshToken(account, true);
|
||||||
|
Task<string> accessTokenTask = jwtService.CreateAccessToken(account);
|
||||||
|
await Task.WhenAll(refreshTokenTask, accessTokenTask);
|
||||||
|
|
||||||
|
AuthenticateResponse response = new AuthenticateResponse
|
||||||
|
{
|
||||||
|
AccessToken = accessTokenTask.Result,
|
||||||
|
RefreshToken = refreshTokenTask.Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
return RequestResult.Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult<AuthenticateResponse>> AuthenticateRefresh()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected byte[] ComputeHash(string password, string leftSalt, string rightSalt) => SHA512.Create().ComputeHash(Encoding.UTF8.GetBytes($"{leftSalt}{password}{rightSalt}"));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
|
||||||
|
<PackageReference Include="SimpleToolkit.Extensions" Version="1.7.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\WatchIt.Shared\WatchIt.Shared.Models\WatchIt.Shared.Models.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.JWT\WatchIt.WebAPI.Services.Utility.JWT.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.WebAPI.Services.Utility.Configuration.Models;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.Configuration
|
||||||
|
{
|
||||||
|
public interface IConfigurationService
|
||||||
|
{
|
||||||
|
ConfigurationData Data { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class ConfigurationService(IConfiguration configuration) : IConfigurationService
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public ConfigurationData Data => configuration.GetSection("WebAPI").Get<ConfigurationData>()!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.Configuration.Models
|
||||||
|
{
|
||||||
|
public class AccessToken
|
||||||
|
{
|
||||||
|
public int Lifetime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.Configuration.Models
|
||||||
|
{
|
||||||
|
public class Authentication
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Issuer { get; set; }
|
||||||
|
public RefreshToken RefreshToken { get; set; }
|
||||||
|
public AccessToken AccessToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.Configuration.Models
|
||||||
|
{
|
||||||
|
public class ConfigurationData
|
||||||
|
{
|
||||||
|
public Authentication Authentication { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.Configuration.Models
|
||||||
|
{
|
||||||
|
public class RefreshToken
|
||||||
|
{
|
||||||
|
public int Lifetime { get; set; }
|
||||||
|
public int ExtendedLifetime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using Microsoft.IdentityModel.JsonWebTokens;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.Database;
|
||||||
|
using WatchIt.Database.Model.Account;
|
||||||
|
using WatchIt.WebAPI.Services.Utility.Configuration;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.JWT
|
||||||
|
{
|
||||||
|
public interface IJWTService
|
||||||
|
{
|
||||||
|
Task<string> CreateAccessToken(Account account);
|
||||||
|
Task<string> CreateRefreshToken(Account account, bool extendable);
|
||||||
|
Task<string> ExtendRefreshToken(Account account, Guid id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class JWTService(IConfigurationService configurationService, DatabaseContext database) : IJWTService
|
||||||
|
{
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task<string> CreateRefreshToken(Account account, bool extendable)
|
||||||
|
{
|
||||||
|
int expirationMinutes = extendable ? configurationService.Data.Authentication.RefreshToken.ExtendedLifetime : configurationService.Data.Authentication.RefreshToken.Lifetime;
|
||||||
|
DateTime expirationDate = DateTime.UtcNow.AddMinutes(expirationMinutes);
|
||||||
|
Guid id = Guid.NewGuid();
|
||||||
|
|
||||||
|
AccountRefreshToken refreshToken = new AccountRefreshToken
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
AccountId = account.Id,
|
||||||
|
ExpirationDate = expirationDate,
|
||||||
|
IsExtendable = extendable
|
||||||
|
};
|
||||||
|
database.AccountRefreshTokens.Add(refreshToken);
|
||||||
|
Task saveTask = database.SaveChangesAsync();
|
||||||
|
|
||||||
|
SecurityTokenDescriptor tokenDescriptor = CreateBaseSecurityTokenDescriptor(account, id, expirationDate);
|
||||||
|
tokenDescriptor.Audience = "refresh";
|
||||||
|
tokenDescriptor.Subject.AddClaim(new Claim("extend", extendable.ToString()));
|
||||||
|
|
||||||
|
string tokenString = TokenToString(tokenDescriptor);
|
||||||
|
|
||||||
|
await saveTask;
|
||||||
|
|
||||||
|
return tokenString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ExtendRefreshToken(Account account, Guid id)
|
||||||
|
{
|
||||||
|
AccountRefreshToken? token = account.AccountRefreshTokens.FirstOrDefault(x => x.Id == id);
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
throw new TokenNotFoundException();
|
||||||
|
}
|
||||||
|
if (!token.IsExtendable)
|
||||||
|
{
|
||||||
|
throw new TokenNotExtendableException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int expirationMinutes = configurationService.Data.Authentication.RefreshToken.ExtendedLifetime;
|
||||||
|
DateTime expirationDate = DateTime.UtcNow.AddMinutes(expirationMinutes);
|
||||||
|
|
||||||
|
token.ExpirationDate = expirationDate;
|
||||||
|
|
||||||
|
Task saveTask = database.SaveChangesAsync();
|
||||||
|
|
||||||
|
SecurityTokenDescriptor tokenDescriptor = CreateBaseSecurityTokenDescriptor(account, id, expirationDate);
|
||||||
|
tokenDescriptor.Audience = "refresh";
|
||||||
|
tokenDescriptor.Subject.AddClaim(new Claim("extend", bool.TrueString));
|
||||||
|
|
||||||
|
string tokenString = TokenToString(tokenDescriptor);
|
||||||
|
|
||||||
|
await saveTask;
|
||||||
|
|
||||||
|
return tokenString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> CreateAccessToken(Account account)
|
||||||
|
{
|
||||||
|
DateTime lifetime = DateTime.Now.AddMinutes(configurationService.Data.Authentication.AccessToken.Lifetime);
|
||||||
|
Guid id = Guid.NewGuid();
|
||||||
|
|
||||||
|
SecurityTokenDescriptor tokenDescriptor = CreateBaseSecurityTokenDescriptor(account, id, lifetime);
|
||||||
|
tokenDescriptor.Audience = "access";
|
||||||
|
tokenDescriptor.Subject.AddClaim(new Claim("admin", account.IsAdmin.ToString()));
|
||||||
|
|
||||||
|
return TokenToString(tokenDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime) => new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, id.ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Email, account.Email),
|
||||||
|
new Claim(JwtRegisteredClaimNames.UniqueName, account.Username),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.ToString()),
|
||||||
|
}),
|
||||||
|
Expires = expirationTime,
|
||||||
|
Issuer = configurationService.Data.Authentication.Issuer,
|
||||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected string TokenToString(SecurityTokenDescriptor tokenDescriptor)
|
||||||
|
{
|
||||||
|
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
|
||||||
|
handler.InboundClaimTypeMap.Clear();
|
||||||
|
|
||||||
|
SecurityToken token = handler.CreateToken(tokenDescriptor);
|
||||||
|
|
||||||
|
return handler.WriteToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.JWT
|
||||||
|
{
|
||||||
|
public class TokenNotExtendableException : Exception
|
||||||
|
{
|
||||||
|
public TokenNotExtendableException() : base() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.JWT
|
||||||
|
{
|
||||||
|
public class TokenNotFoundException : Exception
|
||||||
|
{
|
||||||
|
public TokenNotFoundException() : base() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.1.2" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.1.2" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\..\WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Services.Utility.User
|
||||||
|
{
|
||||||
|
public class UserService(IHttpContextAccessor accessor)
|
||||||
|
{
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.Database;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Authenticate;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts
|
||||||
|
{
|
||||||
|
public class AuthenticateRequestValidator : AbstractValidator<AuthenticateRequest>
|
||||||
|
{
|
||||||
|
#region CONSTRUCTOR
|
||||||
|
|
||||||
|
public AuthenticateRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.UsernameOrEmail).NotEmpty();
|
||||||
|
RuleFor(x => x.Password).NotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WatchIt.Database;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Register;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts
|
||||||
|
{
|
||||||
|
public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
|
||||||
|
{
|
||||||
|
#region CONSTRUCTOR
|
||||||
|
|
||||||
|
public RegisterRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Username).MinimumLength(5)
|
||||||
|
.MaximumLength(50)
|
||||||
|
.CannotBeIn(database.Accounts, x => x.Username).WithMessage("Username was already used");
|
||||||
|
RuleFor(x => x.Email).EmailAddress()
|
||||||
|
.CannotBeIn(database.Accounts, x => x.Email).WithMessage("Email was already used");
|
||||||
|
RuleFor(x => x.Password).MinimumLength(8)
|
||||||
|
.Must(x => x.Any(c => Char.IsUpper(c))).WithMessage("Password must contain at least one uppercase letter.")
|
||||||
|
.Must(x => x.Any(c => Char.IsLower(c))).WithMessage("Password must contain at least one lowercase letter.")
|
||||||
|
.Must(x => x.Any(c => Char.IsDigit(c))).WithMessage("Password must contain at least one digit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
18
WatchIt.WebAPI/WatchIt.WebAPI.Validators/CustomValidators.cs
Normal file
18
WatchIt.WebAPI/WatchIt.WebAPI.Validators/CustomValidators.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators
|
||||||
|
{
|
||||||
|
public static class CustomValidators
|
||||||
|
{
|
||||||
|
public static IRuleBuilderOptions<T, TProperty> CannotBeIn<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, IEnumerable<TProperty> collection) => ruleBuilder.Must(x => !collection.Any(e => Equals(e, x)));
|
||||||
|
public static IRuleBuilderOptions<T, TProperty> CannotBeIn<T, TProperty, TCollectionType>(this IRuleBuilder<T, TProperty> ruleBuilder, IEnumerable<TCollectionType> collection, Func<TCollectionType, TProperty> propertyFunc) => ruleBuilder.Must(x => !collection.Select(propertyFunc).Any(e => Equals(e, x)));
|
||||||
|
public static IRuleBuilderOptions<T, TProperty> MustBeIn<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, IEnumerable<TProperty> collection) => ruleBuilder.Must(x => collection.Any(e => Equals(e, x)));
|
||||||
|
public static IRuleBuilderOptions<T, TProperty> MustBeIn<T, TProperty, TCollectionType>(this IRuleBuilder<T, TProperty> ruleBuilder, IEnumerable<TCollectionType> collection, Func<TCollectionType, TProperty> propertyFunc) => ruleBuilder.Must(x => collection.Select(propertyFunc).Any(e => Equals(e, x)));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
||||||
|
<ProjectReference Include="..\..\WatchIt.Shared\WatchIt.Shared.Models\WatchIt.Shared.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
59
WatchIt.sln
59
WatchIt.sln
@@ -15,7 +15,27 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Database", "WatchIt
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Database.Model", "WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj", "{46A294FF-F15F-4773-9E33-AFFC6DF2148C}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Database.Model", "WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj", "{46A294FF-F15F-4773-9E33-AFFC6DF2148C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Database.DataSeeding", "WatchIt.Database\WatchIt.Database.DataSeeding\WatchIt.Database.DataSeeding.csproj", "{EC685BDC-9C80-4D6D-94DA-F788976CD104}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Database.DataSeeding", "WatchIt.Database\WatchIt.Database.DataSeeding\WatchIt.Database.DataSeeding.csproj", "{EC685BDC-9C80-4D6D-94DA-F788976CD104}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.WebAPI.Controllers", "WatchIt.WebAPI\WatchIt.WebAPI.Controllers\WatchIt.WebAPI.Controllers.csproj", "{F8EC5C47-9866-4065-AA8B-0441280BB1C9}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.Shared", "WatchIt.Shared", "{02132B7F-2055-4FA9-AA94-EDB92159AFF8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Shared.Models", "WatchIt.Shared\WatchIt.Shared.Models\WatchIt.Shared.Models.csproj", "{B3D90254-F594-4C15-B003-F828EEDA850A}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.WebAPI.Services", "WatchIt.WebAPI.Services", "{CD2FA54C-BAF5-41B2-B8C7-5C3EDED1B41A}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.WebAPI.Services.Controllers", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.csproj", "{CE4669C8-E537-400A-A872-A32BD165F1AE}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Validators", "WatchIt.WebAPI\WatchIt.WebAPI.Validators\WatchIt.WebAPI.Validators.csproj", "{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.WebAPI.Services.Utility", "WatchIt.WebAPI.Services.Utility", "{70058164-43EB-47E5-8507-23D6A41A2581}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Utility.Configuration", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj", "{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Utility.JWT", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.JWT\WatchIt.WebAPI.Services.Utility.JWT.csproj", "{68A3C906-7470-40A1-B9C3-2F8963AA7E44}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Utility.User", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.User\WatchIt.WebAPI.Services.Utility.User.csproj", "{FA335A3A-BD57-472A-B3ED-43F18D9F31A6}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -39,6 +59,34 @@ Global
|
|||||||
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EC685BDC-9C80-4D6D-94DA-F788976CD104}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F8EC5C47-9866-4065-AA8B-0441280BB1C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8EC5C47-9866-4065-AA8B-0441280BB1C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F8EC5C47-9866-4065-AA8B-0441280BB1C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F8EC5C47-9866-4065-AA8B-0441280BB1C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B3D90254-F594-4C15-B003-F828EEDA850A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B3D90254-F594-4C15-B003-F828EEDA850A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B3D90254-F594-4C15-B003-F828EEDA850A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B3D90254-F594-4C15-B003-F828EEDA850A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CE4669C8-E537-400A-A872-A32BD165F1AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CE4669C8-E537-400A-A872-A32BD165F1AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CE4669C8-E537-400A-A872-A32BD165F1AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CE4669C8-E537-400A-A872-A32BD165F1AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{68A3C906-7470-40A1-B9C3-2F8963AA7E44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{68A3C906-7470-40A1-B9C3-2F8963AA7E44}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{68A3C906-7470-40A1-B9C3-2F8963AA7E44}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{68A3C906-7470-40A1-B9C3-2F8963AA7E44}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FA335A3A-BD57-472A-B3ED-43F18D9F31A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FA335A3A-BD57-472A-B3ED-43F18D9F31A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FA335A3A-BD57-472A-B3ED-43F18D9F31A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FA335A3A-BD57-472A-B3ED-43F18D9F31A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -47,6 +95,15 @@ Global
|
|||||||
{8CDAA140-05FC-4EB7-A9F5-A85032C8FD2F} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
{8CDAA140-05FC-4EB7-A9F5-A85032C8FD2F} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
||||||
{46A294FF-F15F-4773-9E33-AFFC6DF2148C} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
{46A294FF-F15F-4773-9E33-AFFC6DF2148C} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
||||||
{EC685BDC-9C80-4D6D-94DA-F788976CD104} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
{EC685BDC-9C80-4D6D-94DA-F788976CD104} = {98C91E27-2C36-4C74-A80F-9ACD7F28BC54}
|
||||||
|
{F8EC5C47-9866-4065-AA8B-0441280BB1C9} = {76B40EBF-8054-4A15-ABE8-141E1CCA6E4E}
|
||||||
|
{B3D90254-F594-4C15-B003-F828EEDA850A} = {02132B7F-2055-4FA9-AA94-EDB92159AFF8}
|
||||||
|
{CD2FA54C-BAF5-41B2-B8C7-5C3EDED1B41A} = {76B40EBF-8054-4A15-ABE8-141E1CCA6E4E}
|
||||||
|
{CE4669C8-E537-400A-A872-A32BD165F1AE} = {CD2FA54C-BAF5-41B2-B8C7-5C3EDED1B41A}
|
||||||
|
{E7DE3C54-B199-4DF8-ADA0-46FE85AA059F} = {76B40EBF-8054-4A15-ABE8-141E1CCA6E4E}
|
||||||
|
{70058164-43EB-47E5-8507-23D6A41A2581} = {CD2FA54C-BAF5-41B2-B8C7-5C3EDED1B41A}
|
||||||
|
{9915A61F-A7CA-4F6F-A213-4B31BE81C3DF} = {70058164-43EB-47E5-8507-23D6A41A2581}
|
||||||
|
{68A3C906-7470-40A1-B9C3-2F8963AA7E44} = {70058164-43EB-47E5-8507-23D6A41A2581}
|
||||||
|
{FA335A3A-BD57-472A-B3ED-43F18D9F31A6} = {70058164-43EB-47E5-8507-23D6A41A2581}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {1D253492-C786-4DD9-80B5-7DE51D4D3304}
|
SolutionGuid = {1D253492-C786-4DD9-80B5-7DE51D4D3304}
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Identity.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using WatchIt.Database;
|
using WatchIt.Database;
|
||||||
|
using WatchIt.Shared.Models.Accounts.Register;
|
||||||
|
using WatchIt.WebAPI.Services.Controllers;
|
||||||
|
using WatchIt.WebAPI.Services.Utility.Configuration;
|
||||||
|
using WatchIt.WebAPI.Services.Utility.JWT;
|
||||||
using WatchIt.Website;
|
using WatchIt.Website;
|
||||||
|
|
||||||
namespace WatchIt
|
namespace WatchIt
|
||||||
@@ -23,10 +34,7 @@ namespace WatchIt
|
|||||||
ConfigureLogging();
|
ConfigureLogging();
|
||||||
ConfigureDatabase();
|
ConfigureDatabase();
|
||||||
ConfigureWebAPI();
|
ConfigureWebAPI();
|
||||||
|
ConfigureWebsite();
|
||||||
// Add services to the container.
|
|
||||||
_builder.Services.AddRazorComponents()
|
|
||||||
.AddInteractiveServerComponents();
|
|
||||||
|
|
||||||
var app = _builder.Build();
|
var app = _builder.Build();
|
||||||
|
|
||||||
@@ -85,11 +93,86 @@ namespace WatchIt
|
|||||||
|
|
||||||
protected static void ConfigureWebAPI()
|
protected static void ConfigureWebAPI()
|
||||||
{
|
{
|
||||||
_builder.Services.AddControllers();
|
_builder.Services.AddValidatorsFromAssembly(Assembly.Load("WatchIt.Shared.Models"));
|
||||||
|
|
||||||
|
_builder.Services.AddAuthentication(x =>
|
||||||
|
{
|
||||||
|
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddJwtBearer(x =>
|
||||||
|
{
|
||||||
|
x.RequireHttpsMetadata = false;
|
||||||
|
x.SaveToken = true;
|
||||||
|
x.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_builder.Configuration.GetValue<string>("WebAPI:Authentication:Key"))),
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidAudience = "access",
|
||||||
|
ValidIssuer = _builder.Configuration.GetValue<string>("WebAPI:Authentication:Issuer"),
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ClockSkew = TimeSpan.FromMinutes(1),
|
||||||
|
};
|
||||||
|
x.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnAuthenticationFailed = context =>
|
||||||
|
{
|
||||||
|
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add("Token-Expired", "true");
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.AddJwtBearer("refresh", x =>
|
||||||
|
{
|
||||||
|
x.RequireHttpsMetadata = false;
|
||||||
|
x.SaveToken = true;
|
||||||
|
x.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_builder.Configuration.GetValue<string>("WebAPI:Authentication:Key"))),
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidIssuer = _builder.Configuration.GetValue<string>("WebAPI:Authentication:Issuer"),
|
||||||
|
ValidAudience = "refresh",
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ClockSkew = TimeSpan.FromMinutes(1)
|
||||||
|
};
|
||||||
|
x.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnAuthenticationFailed = context =>
|
||||||
|
{
|
||||||
|
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add("Token-Expired", "true");
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
_builder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
_builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
_builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||||
|
_builder.Services.AddSingleton<IJWTService, JWTService>();
|
||||||
|
|
||||||
|
_builder.Services.AddSingleton<IAccountsControllerService, AccountsControllerService>();
|
||||||
|
|
||||||
|
_builder.Services.AddFluentValidationAutoValidation();
|
||||||
_builder.Services.AddEndpointsApiExplorer();
|
_builder.Services.AddEndpointsApiExplorer();
|
||||||
|
_builder.Services.AddControllers();
|
||||||
_builder.Services.AddSwaggerGen();
|
_builder.Services.AddSwaggerGen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void ConfigureWebsite()
|
||||||
|
{
|
||||||
|
_builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentValidation" Version="11.9.0" />
|
||||||
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -26,7 +31,15 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\WatchIt.Database\WatchIt.Database.DataSeeding\WatchIt.Database.DataSeeding.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj" />
|
||||||
<ProjectReference Include="..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
<ProjectReference Include="..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.Shared\WatchIt.Shared.Models\WatchIt.Shared.Models.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI\WatchIt.WebAPI.Controllers\WatchIt.WebAPI.Controllers.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.JWT\WatchIt.WebAPI.Services.Utility.JWT.csproj" />
|
||||||
|
<ProjectReference Include="..\WatchIt.WebAPI\WatchIt.WebAPI.Validators\WatchIt.WebAPI.Validators.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace WatchIt.WebAPI
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/accounts")]
|
|
||||||
public class AccountsController : ControllerBase
|
|
||||||
{
|
|
||||||
[HttpPost]
|
|
||||||
[Route("create-account")]
|
|
||||||
public async Task CreateAccount()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,28 @@
|
|||||||
{
|
{
|
||||||
|
"WebAPI": {
|
||||||
|
"Authentication": {
|
||||||
|
"Key": "testkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytestkeytest",
|
||||||
|
"Issuer": "WatchIt",
|
||||||
|
"RefreshToken": {
|
||||||
|
"Lifetime": 1440,
|
||||||
|
"ExtendedLifetime": 10080
|
||||||
|
},
|
||||||
|
"AccessToken": {
|
||||||
|
"Lifetime": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Website": {
|
||||||
|
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"Default": "Host=localhost;Database=watchit;Username=watchit;Password=Xdv2Etchavbuuho"
|
"Default": "Host=localhost;Database=watchit;Username=watchit;Password=Xdv2Etchavbuuho"
|
||||||
}
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
18
genre.json
18
genre.json
@@ -1,18 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"Id": 1,
|
|
||||||
"Name": "Comedy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": 2,
|
|
||||||
"Name": "Thriller"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": 3,
|
|
||||||
"Name": "Horror"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": 4,
|
|
||||||
"Name": "Action"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user