Auth DB schema migration, ef design, auth
This commit is contained in:
parent
a0d5df1c52
commit
5e4bfaa95e
22 changed files with 1106 additions and 38 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
**/obj
|
||||
**/bin
|
||||
**.db
|
||||
**.db-wal
|
||||
**.db-shm
|
||||
|
|
39
.vscode/launch.json
vendored
Normal file
39
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Core Server",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/src/Interlinked.User/bin/Debug/net7.0/Interlinked.User.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/Interlinked.User",
|
||||
"stopAtEntry": false,
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
},
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceFolder}",
|
||||
"pipeProgram": "/usr/bin/bash",
|
||||
"pipeArgs": ["-c"],
|
||||
"debuggerPath": "/usr/bin/netcoredbg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/Interlinked.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/Interlinked.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/Interlinked.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -14,4 +14,8 @@
|
|||
<PackageReference Include="Syncfusion.Blazor.Maps" Version="23.1.38" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Interlinked.Shared\Interlinked.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
77
src/Interlinked.Core/Pages/Login.razor
Normal file
77
src/Interlinked.Core/Pages/Login.razor
Normal file
|
@ -0,0 +1,77 @@
|
|||
@page "/login"
|
||||
@using System.ComponentModel.DataAnnotations;
|
||||
@using System.Net
|
||||
@inject HttpClient req
|
||||
@inject UserManager usr
|
||||
@inject NavigationManager nav
|
||||
|
||||
<PageTitle>Login</PageTitle>
|
||||
|
||||
<h1>Log in</h1>
|
||||
|
||||
<EditForm class="form" Model="@Model" OnValidSubmit="@Submit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<div class="form-group">
|
||||
<label for="email">E-mail</label>
|
||||
<InputText @bind-Value="Model!.Email" id="email" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pwd">Password</label>
|
||||
<InputText type="password" @bind-Value="Model!.Password" id="pwd" />
|
||||
</div>
|
||||
<button type="submit">Log in</button>
|
||||
</EditForm>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(ValidationMsg))
|
||||
{
|
||||
<p class="text-danger">@ValidationMsg</p>
|
||||
}
|
||||
|
||||
@code {
|
||||
public record LoginModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = default!;
|
||||
[Required]
|
||||
public string Password { get; set; } = default!;
|
||||
};
|
||||
public LoginModel Model { get; set; } = new();
|
||||
public string ValidationMsg = string.Empty;
|
||||
|
||||
protected override void OnInitialized() => Model ??= new();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
var resp = await req.PostAsJsonAsync("/api/auth/login", Model);
|
||||
if (resp.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
Model = new();
|
||||
ValidationMsg = "Successful login <3";
|
||||
var user = await resp.Content.ReadFromJsonAsync<UserModel>();
|
||||
if (user == null)
|
||||
{
|
||||
ValidationMsg = "Unknown authentication error";
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
usr.User = user;
|
||||
this.StateHasChanged();
|
||||
nav.NavigateTo("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
Model = new();
|
||||
ValidationMsg = "Invalid username or password.";
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
Model = new();
|
||||
ValidationMsg = "Authorization error...";
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
17
src/Interlinked.Core/Pages/Logout.razor
Normal file
17
src/Interlinked.Core/Pages/Logout.razor
Normal file
|
@ -0,0 +1,17 @@
|
|||
@page "/logout"
|
||||
@inject HttpClient req
|
||||
@inject UserManager usr
|
||||
@inject NavigationManager nav
|
||||
@using System.Net
|
||||
|
||||
@code {
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var resp = await req.GetAsync("/api/auth/logout");
|
||||
if (resp.StatusCode == HttpStatusCode.NoContent)
|
||||
{
|
||||
usr.User = null!;
|
||||
}
|
||||
nav.NavigateTo("/login");
|
||||
}
|
||||
}
|
88
src/Interlinked.Core/Pages/Register.razor
Normal file
88
src/Interlinked.Core/Pages/Register.razor
Normal file
|
@ -0,0 +1,88 @@
|
|||
@page "/register"
|
||||
@using System.ComponentModel.DataAnnotations;
|
||||
@using System.Net
|
||||
@inject HttpClient req
|
||||
@inject UserManager usr
|
||||
@inject NavigationManager nav
|
||||
|
||||
<PageTitle>Register</PageTitle>
|
||||
|
||||
<h1>Register</h1>
|
||||
|
||||
<EditForm class="form" Model="@Model" OnValidSubmit="@Submit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<InputText @bind-Value="Model!.Username" id="username" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Full name</label>
|
||||
<InputText @bind-Value="Model!.Name" id="name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">E-mail</label>
|
||||
<InputText @bind-Value="Model!.Email" id="email" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pwd">Password</label>
|
||||
<InputText type="password" @bind-Value="Model!.Password" id="pwd" />
|
||||
</div>
|
||||
<button type="submit">Log in</button>
|
||||
</EditForm>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(ValidationMsg))
|
||||
{
|
||||
<p class="text-danger">@ValidationMsg</p>
|
||||
}
|
||||
|
||||
@code {
|
||||
public record LoginModel
|
||||
{
|
||||
[Required]
|
||||
public string Username { get; set; } = default!;
|
||||
public string? Name { get; set; }
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = default!;
|
||||
[Required]
|
||||
public string Password { get; set; } = default!;
|
||||
};
|
||||
public LoginModel Model { get; set; } = new();
|
||||
public string ValidationMsg = string.Empty;
|
||||
|
||||
protected override void OnInitialized() => Model ??= new();
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
var resp = await req.PostAsJsonAsync("/api/auth/login", Model);
|
||||
if (resp.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
Model = new();
|
||||
ValidationMsg = "Successful login <3"; var user = await resp.Content.ReadFromJsonAsync<UserModel>();
|
||||
if (user == null)
|
||||
{
|
||||
ValidationMsg = "Unknown authentication error";
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
usr.User = user;
|
||||
this.StateHasChanged();
|
||||
nav.NavigateTo("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
Model = new();
|
||||
ValidationMsg = "Invalid username or password.";
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
Model = new();
|
||||
ValidationMsg = "Authorization error...";
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using Interlinked.Core;
|
|||
using Syncfusion.Blazor;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Interlinked.Core.Services;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
|
@ -21,6 +22,13 @@ internal class Program
|
|||
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
|
||||
new[] { "application/octet-stream" });
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<UserManager, UserManager>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
await app.Services.GetRequiredService<UserManager>().InitAsync();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
}
|
32
src/Interlinked.Core/Services/UserManager.cs
Normal file
32
src/Interlinked.Core/Services/UserManager.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Interlinked.Shared.Model;
|
||||
|
||||
namespace Interlinked.Core.Services;
|
||||
|
||||
public class UserManager
|
||||
{
|
||||
public UserManager(IServiceProvider serv)
|
||||
{
|
||||
_serv = serv;
|
||||
}
|
||||
|
||||
private readonly IServiceProvider _serv;
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
using var scope = _serv.CreateAsyncScope();
|
||||
var resp = await scope.ServiceProvider.GetRequiredService<HttpClient>().GetAsync("/api/auth/userdata");
|
||||
if (resp.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
User = (await resp.Content.ReadFromJsonAsync<UserModel>())!;
|
||||
}
|
||||
}
|
||||
|
||||
public UserModel User { get; set; } = default!;
|
||||
public bool IsAuthorized => User != null;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@inherits LayoutComponentBase
|
||||
@inject UserManager usr
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
|
@ -7,7 +8,15 @@
|
|||
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
|
||||
@if (!usr.IsAuthorized)
|
||||
{
|
||||
<a href="/login">Log in</a>
|
||||
<a href="/register">Register</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/logout">Log out (@usr.User.Username)</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
|
|
|
@ -8,3 +8,5 @@
|
|||
@using Microsoft.JSInterop
|
||||
@using Interlinked.Core
|
||||
@using Interlinked.Core.Shared
|
||||
@using Interlinked.Core.Services
|
||||
@using Interlinked.Shared.Model
|
||||
|
|
|
@ -10,7 +10,7 @@ public class AuthSession
|
|||
public required byte[] Key { get; set; }
|
||||
public required byte[] Secret { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public IPAddress LastAddress { get; set; } = default!;
|
||||
public IPAddress? LastAddress { get; set; } = default!;
|
||||
public UserModel User { get; set; } = default!;
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Security.Cryptography;
|
||||
using Interlinked.Shared.Model;
|
||||
using Interlinked.User.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -10,20 +11,26 @@ namespace Interlinked.User.Controllers;
|
|||
[Route("api/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
public AuthController(StoreContext db)
|
||||
public AuthController(StoreContext db, AuthManager auth)
|
||||
{
|
||||
_db = db;
|
||||
_auth = auth;
|
||||
}
|
||||
private readonly StoreContext _db;
|
||||
private readonly AuthManager _auth;
|
||||
|
||||
public record RegisterRequest(string email, string username, string? name, string password);
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> Register([FromBody] string email, [FromBody] string username, [FromBody] string? name, [FromBody] string password)
|
||||
public async Task<IActionResult> Register([FromBody] RegisterRequest req)
|
||||
{
|
||||
email = email.ToLower().Trim();
|
||||
username = username.Trim();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if (_db.Users.AsNoTracking().Any(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)))
|
||||
string email = req.email.ToLower().Trim();
|
||||
string username = req.username.Trim();
|
||||
|
||||
if (_db.Users.AsNoTracking().Any(u => u.Username == username))
|
||||
{
|
||||
return Conflict("Username taken.");
|
||||
}
|
||||
|
@ -35,46 +42,70 @@ public class AuthController : ControllerBase
|
|||
|
||||
byte[] salt = new byte[16];
|
||||
RandomNumberGenerator.Fill(salt);
|
||||
byte[] hash = UserModel.HashPassword(password, salt.ToArray());
|
||||
byte[] hash = UserModel.HashPassword(req.password, salt.ToArray());
|
||||
|
||||
UserModel u = new()
|
||||
{
|
||||
Email = email,
|
||||
Username = username,
|
||||
Salt = salt,
|
||||
Name = name,
|
||||
Hash = hash
|
||||
Name = req.name,
|
||||
Hash = hash,
|
||||
CreatedTimestamp = now
|
||||
};
|
||||
|
||||
//throw new NotImplementedException();
|
||||
await _db.Users.AddAsync(u);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
await _auth.CreateSession(u);
|
||||
|
||||
return Ok(u);
|
||||
}
|
||||
|
||||
public record LoginRequest(string email, string password);
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] string email, [FromBody] string password)
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest req)
|
||||
{
|
||||
email = email.ToLower().Trim();
|
||||
UserModel? u = _db.Users.AsNoTracking().FirstOrDefault(u => email.Equals(u.Email));
|
||||
if ((await _auth.RetrieveCookie()) is not null)
|
||||
{
|
||||
return this.Conflict("Already authenticated...");
|
||||
}
|
||||
string email = req.email.ToLower().Trim();
|
||||
UserModel? u = _db.Users.AsNoTracking().FirstOrDefault(u => email == u.Email);
|
||||
|
||||
if (u is null || !u.CheckHash(password))
|
||||
if (u is null || !u.CheckHash(req.password))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
|
||||
//Create auth session
|
||||
throw new NotImplementedException();
|
||||
await _auth.CreateSession(u);
|
||||
return Ok(u);
|
||||
}
|
||||
|
||||
[HttpGet("userdata")]
|
||||
public async Task<IActionResult> GetUserData()
|
||||
{
|
||||
if (await _auth.RetrieveCookie() is not AuthSession sess)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok(await _db.Users.AsNoTracking().SingleAsync(s => s.Id == sess.UserId));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (await _auth.RetrieveCookie() is AuthSession sess)
|
||||
{
|
||||
await _auth.RevokeSession(sess);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
|
||||
|
|
18
src/Interlinked.User/Controllers/SystemController.cs
Normal file
18
src/Interlinked.User/Controllers/SystemController.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Interlinked.User.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/")]
|
||||
public class SystemController : ControllerBase
|
||||
{
|
||||
[HttpGet("version")]
|
||||
public async Task<IActionResult> Version()
|
||||
{
|
||||
return Ok("1.0E");
|
||||
}
|
||||
}
|
|
@ -7,16 +7,23 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Interlinked.Shared\Interlinked.Shared.csproj" />
|
||||
<ProjectReference Include="..\Interlinked.Core\Interlinked.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
222
src/Interlinked.User/Migrations/20231001074047_Initial schema.Designer.cs
generated
Normal file
222
src/Interlinked.User/Migrations/20231001074047_Initial schema.Designer.cs
generated
Normal file
|
@ -0,0 +1,222 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Interlinked.User;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Interlinked.User.Migrations
|
||||
{
|
||||
[DbContext(typeof(StoreContext))]
|
||||
[Migration("20231001074047_Initial schema")]
|
||||
partial class Initialschema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.11");
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
||||
{
|
||||
b.Property<byte[]>("Key")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("LastAddress")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Secret")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AuthSessions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Identifier");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("City")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CountryCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedTimestamp")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DayOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Gender")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(96)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT")
|
||||
.UseCollation("NOCASE");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ProjectTag", b =>
|
||||
{
|
||||
b.Property<long>("ProjectsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TagsIdentifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ProjectsId", "TagsIdentifier");
|
||||
|
||||
b.HasIndex("TagsIdentifier");
|
||||
|
||||
b.ToTable("ProjectTag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TagUserModel", b =>
|
||||
{
|
||||
b.Property<string>("InterestsIdentifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("UsersId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InterestsIdentifier", "UsersId");
|
||||
|
||||
b.HasIndex("UsersId");
|
||||
|
||||
b.ToTable("TagUserModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", "User")
|
||||
.WithMany("AuthSessions")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ProjectTag", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.Project", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagsIdentifier")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TagUserModel", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("InterestsIdentifier")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UsersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
||||
{
|
||||
b.Navigation("AuthSessions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
180
src/Interlinked.User/Migrations/20231001074047_Initial schema.cs
Normal file
180
src/Interlinked.User/Migrations/20231001074047_Initial schema.cs
Normal file
|
@ -0,0 +1,180 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Interlinked.User.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initialschema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tags",
|
||||
columns: table => new
|
||||
{
|
||||
Identifier = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tags", x => x.Identifier);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Username = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false, collation: "NOCASE"),
|
||||
Email = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 96, nullable: true),
|
||||
Salt = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||
Hash = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||
City = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CountryCode = table.Column<string>(type: "TEXT", maxLength: 2, nullable: false),
|
||||
Gender = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DayOfBirth = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
CreatedTimestamp = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AuthSessions",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||
Secret = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||
UserId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
LastAddress = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AuthSessions", x => x.Key);
|
||||
table.ForeignKey(
|
||||
name: "FK_AuthSessions_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Projects",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Title = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
OwnerId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Projects", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Projects_Users_OwnerId",
|
||||
column: x => x.OwnerId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TagUserModel",
|
||||
columns: table => new
|
||||
{
|
||||
InterestsIdentifier = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UsersId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TagUserModel", x => new { x.InterestsIdentifier, x.UsersId });
|
||||
table.ForeignKey(
|
||||
name: "FK_TagUserModel_Tags_InterestsIdentifier",
|
||||
column: x => x.InterestsIdentifier,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Identifier",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_TagUserModel_Users_UsersId",
|
||||
column: x => x.UsersId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ProjectTag",
|
||||
columns: table => new
|
||||
{
|
||||
ProjectsId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
TagsIdentifier = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ProjectTag", x => new { x.ProjectsId, x.TagsIdentifier });
|
||||
table.ForeignKey(
|
||||
name: "FK_ProjectTag_Projects_ProjectsId",
|
||||
column: x => x.ProjectsId,
|
||||
principalTable: "Projects",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ProjectTag_Tags_TagsIdentifier",
|
||||
column: x => x.TagsIdentifier,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Identifier",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AuthSessions_UserId",
|
||||
table: "AuthSessions",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Projects_OwnerId",
|
||||
table: "Projects",
|
||||
column: "OwnerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ProjectTag_TagsIdentifier",
|
||||
table: "ProjectTag",
|
||||
column: "TagsIdentifier");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TagUserModel_UsersId",
|
||||
table: "TagUserModel",
|
||||
column: "UsersId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AuthSessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ProjectTag");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TagUserModel");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Projects");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
219
src/Interlinked.User/Migrations/StoreContextModelSnapshot.cs
Normal file
219
src/Interlinked.User/Migrations/StoreContextModelSnapshot.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Interlinked.User;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Interlinked.User.Migrations
|
||||
{
|
||||
[DbContext(typeof(StoreContext))]
|
||||
partial class StoreContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.11");
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
||||
{
|
||||
b.Property<byte[]>("Key")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("LastAddress")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Secret")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AuthSessions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Identifier");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("City")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CountryCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedTimestamp")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DayOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Gender")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(96)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT")
|
||||
.UseCollation("NOCASE");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ProjectTag", b =>
|
||||
{
|
||||
b.Property<long>("ProjectsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TagsIdentifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ProjectsId", "TagsIdentifier");
|
||||
|
||||
b.HasIndex("TagsIdentifier");
|
||||
|
||||
b.ToTable("ProjectTag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TagUserModel", b =>
|
||||
{
|
||||
b.Property<string>("InterestsIdentifier")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("UsersId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("InterestsIdentifier", "UsersId");
|
||||
|
||||
b.HasIndex("UsersId");
|
||||
|
||||
b.ToTable("TagUserModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", "User")
|
||||
.WithMany("AuthSessions")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ProjectTag", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.Project", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ProjectsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagsIdentifier")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TagUserModel", b =>
|
||||
{
|
||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("InterestsIdentifier")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Interlinked.Shared.Model.UserModel", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UsersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
||||
{
|
||||
b.Navigation("AuthSessions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
31
src/Interlinked.User/Models/UserDomain.cs
Normal file
31
src/Interlinked.User/Models/UserDomain.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Interlinked.Shared.Model;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Interlinked.User.Models;
|
||||
|
||||
public class UserDomain : IdentityUser<long>
|
||||
{
|
||||
[EmailAddress]
|
||||
public required string Email { get; set; }
|
||||
[MaxLength(96)]
|
||||
public string? Name { get; set; }
|
||||
[JsonIgnore]
|
||||
public byte[] Salt { get; set; } = default!;
|
||||
[JsonIgnore]
|
||||
public byte[] Hash { get; set; } = default!;
|
||||
public string? City { get; set; }
|
||||
[StringLength(2)]
|
||||
public string CountryCode { get; set; } = "XX";
|
||||
public string? Gender { get; set; } = "cat";
|
||||
public DateTime? DayOfBirth { get; set; }
|
||||
public ICollection<Tag> Interests { get; set; } = default!;
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public DateTime CreatedTimestamp { get; set; }
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
using Interlinked.Shared.Model;
|
||||
using Interlinked.User;
|
||||
using Interlinked.User.Models;
|
||||
using Interlinked.User.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddDbContext<StoreContext>();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddDefaultIdentity<UserModel>(o => o.SignIn.RequireConfirmedAccount = true)
|
||||
builder.Services.AddDefaultIdentity<UserDomain>(o => o.SignIn.RequireConfirmedAccount = true)
|
||||
.AddEntityFrameworkStores<StoreContext>();
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
|
@ -20,14 +22,34 @@ builder.Services.ConfigureApplicationCookie(options =>
|
|||
options.AccessDeniedPath = "/auth/account/uwuless";
|
||||
options.SlidingExpiration = true;
|
||||
});
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<AuthManager, AuthManager>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<StoreContext>();
|
||||
db.Database.Migrate();
|
||||
}
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
app.Run();
|
||||
|
|
|
@ -11,8 +11,8 @@ namespace Interlinked.User.Services;
|
|||
|
||||
public class AuthManager
|
||||
{
|
||||
public const string AUTH_COOKIE_NAME = "bOiKissEr_ᓚᘏᗢ_UwU";
|
||||
public AuthManager(StoreContext db, HttpContext http, IMemoryCache cache)
|
||||
public const string AUTH_COOKIE_NAME = "bOiKissEr_UwU";
|
||||
public AuthManager(StoreContext db, IHttpContextAccessor http, IMemoryCache cache)
|
||||
{
|
||||
_db = db;
|
||||
_http = http;
|
||||
|
@ -22,7 +22,7 @@ public class AuthManager
|
|||
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly StoreContext _db;
|
||||
private readonly HttpContext _http;
|
||||
private readonly IHttpContextAccessor _http;
|
||||
|
||||
public async Task<AuthSession> CreateSession(UserModel user)
|
||||
{
|
||||
|
@ -33,13 +33,14 @@ public class AuthManager
|
|||
{
|
||||
UserId = user.Id,
|
||||
Secret = secret,
|
||||
Key = key
|
||||
Key = key,
|
||||
LastAddress = _http.HttpContext!.Connection.RemoteIpAddress
|
||||
};
|
||||
|
||||
await _db.AuthSessions.AddAsync(sess);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
_http.Response.Cookies.Append(AUTH_COOKIE_NAME, sess.ToString());
|
||||
_http.HttpContext!.Response.Cookies.Append(AUTH_COOKIE_NAME, sess.ToString());
|
||||
return sess;
|
||||
}
|
||||
|
||||
|
@ -47,17 +48,22 @@ public class AuthManager
|
|||
{
|
||||
_db.AuthSessions.Remove(sess);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
_http.HttpContext!.Response.Cookies.Delete(AUTH_COOKIE_NAME);
|
||||
}
|
||||
|
||||
public async Task<AuthSession?> RetrieveCookie()
|
||||
{
|
||||
if (!_http.Request.Cookies.TryGetValue(AUTH_COOKIE_NAME, out string? val) || val is null)
|
||||
if (!_http.HttpContext!.Request.Cookies.TryGetValue(AUTH_COOKIE_NAME, out string? val) || val is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
int idx = val.IndexOf('.');
|
||||
string keyStr = val[0..(idx - 1)], hashStr = val;
|
||||
string keyStr = val[0..idx], hashStr = val[(idx + 1)..];
|
||||
byte[] key = Convert.FromBase64String(keyStr), hash = Convert.FromBase64String(hashStr);
|
||||
|
||||
var sess = await _db.AuthSessions.SingleOrDefaultAsync(s => s.Key.SequenceEqual(key));
|
||||
|
@ -72,4 +78,9 @@ public class AuthManager
|
|||
|
||||
return trueHash.SequenceEqual(hash) ? sess : null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,17 @@ public class StoreContext : DbContext
|
|||
public DbSet<Tag> Tags => Set<Tag>();
|
||||
public DbSet<AuthSession> AuthSessions => Set<AuthSession>();
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder o)
|
||||
{
|
||||
o.UseSqlite("Data Source=./store.db;Cache=Shared");
|
||||
o.EnableDetailedErrors();
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<UserModel>().HasMany(u => u.AuthSessions).WithOne(a => a.User);
|
||||
mb.Entity<UserModel>().HasMany(u => u.Interests).WithMany(t => t.Users);
|
||||
mb.Entity<UserModel>().Property(p => p.Username).UseCollation("NOCASE");
|
||||
mb.Entity<Project>().HasMany(p => p.Tags).WithMany(t => t.Projects);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue