auth changes
This commit is contained in:
@@ -22,8 +22,8 @@ namespace WatchIt.Database.Model.Account
|
||||
public long Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Description { get; set; }
|
||||
public short GenderId { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public short? GenderId { get; set; }
|
||||
public Guid? ProfilePictureId { get; set; }
|
||||
public Guid? BackgroundPictureId { get; set; }
|
||||
public byte[] Password { get; set; }
|
||||
@@ -49,6 +49,8 @@ namespace WatchIt.Database.Model.Account
|
||||
public IEnumerable<RatingMediaSeriesSeason> RatingMediaSeriesSeason { get; set; }
|
||||
public IEnumerable<RatingMediaSeriesEpisode> RatingMediaSeriesEpisode { get; set; }
|
||||
|
||||
public IEnumerable<AccountRefreshToken> AccountRefreshTokens { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -76,10 +78,8 @@ namespace WatchIt.Database.Model.Account
|
||||
|
||||
builder.HasOne(x => x.Gender)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.GenderId)
|
||||
.IsRequired();
|
||||
builder.Property(x => x.GenderId)
|
||||
.IsRequired();
|
||||
.HasForeignKey(x => x.GenderId);
|
||||
builder.Property(x => x.GenderId);
|
||||
|
||||
builder.HasOne(x => x.ProfilePicture)
|
||||
.WithOne(x => x.Account)
|
||||
@@ -104,7 +104,8 @@ namespace WatchIt.Database.Model.Account
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.IsAdmin)
|
||||
.IsRequired();
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.CreationDate)
|
||||
.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
|
||||
public virtual DbSet<Account> Accounts { get; set; }
|
||||
public virtual DbSet<AccountProfilePicture> AccountProfilePictures { get; set; }
|
||||
public virtual DbSet<AccountRefreshToken> AccountRefreshTokens { get; set; }
|
||||
|
||||
// Media
|
||||
public virtual DbSet<Media> Media { get; set; }
|
||||
@@ -87,6 +88,7 @@ namespace WatchIt.Database
|
||||
// Account
|
||||
EntityBuilder.Build<Account>(modelBuilder);
|
||||
EntityBuilder.Build<AccountProfilePicture>(modelBuilder);
|
||||
EntityBuilder.Build<AccountRefreshToken>(modelBuilder);
|
||||
|
||||
// Media
|
||||
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()");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
@@ -48,11 +47,13 @@ namespace WatchIt.Database.Migrations
|
||||
.HasMaxLength(320)
|
||||
.HasColumnType("character varying(320)");
|
||||
|
||||
b.Property<short>("GenderId")
|
||||
b.Property<short?>("GenderId")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("boolean");
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<DateTime>("LastActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -126,6 +127,31 @@ namespace WatchIt.Database.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<short>("Id")
|
||||
@@ -891,9 +917,7 @@ namespace WatchIt.Database.Migrations
|
||||
|
||||
b.HasOne("WatchIt.Database.Model.Common.Gender", "Gender")
|
||||
.WithMany()
|
||||
.HasForeignKey("GenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("GenderId");
|
||||
|
||||
b.HasOne("WatchIt.Database.Model.Account.AccountProfilePicture", "ProfilePicture")
|
||||
.WithOne("Account")
|
||||
@@ -906,6 +930,17 @@ namespace WatchIt.Database.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("WatchIt.Database.Model.Media.MediaMovie", null)
|
||||
@@ -1188,6 +1223,8 @@ namespace WatchIt.Database.Migrations
|
||||
|
||||
modelBuilder.Entity("WatchIt.Database.Model.Account.Account", b =>
|
||||
{
|
||||
b.Navigation("AccountRefreshTokens");
|
||||
|
||||
b.Navigation("RatingMedia");
|
||||
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WatchIt.Database.Model", "WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.csproj", "{46A294FF-F15F-4773-9E33-AFFC6DF2148C}"
|
||||
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
|
||||
Global
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -47,6 +95,15 @@ Global
|
||||
{8CDAA140-05FC-4EB7-A9F5-A85032C8FD2F} = {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}
|
||||
{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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
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.IdentityModel.Tokens;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
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;
|
||||
|
||||
namespace WatchIt
|
||||
@@ -23,10 +34,7 @@ namespace WatchIt
|
||||
ConfigureLogging();
|
||||
ConfigureDatabase();
|
||||
ConfigureWebAPI();
|
||||
|
||||
// Add services to the container.
|
||||
_builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
ConfigureWebsite();
|
||||
|
||||
var app = _builder.Build();
|
||||
|
||||
@@ -85,11 +93,86 @@ namespace WatchIt
|
||||
|
||||
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.AddControllers();
|
||||
_builder.Services.AddSwaggerGen();
|
||||
}
|
||||
|
||||
protected static void ConfigureWebsite()
|
||||
{
|
||||
_builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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.Design" Version="8.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -26,7 +31,15 @@
|
||||
</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.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>
|
||||
|
||||
</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": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"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