backend
This commit is contained in:
parent
19aeb5aba9
commit
66e32907fe
19 changed files with 317 additions and 193 deletions
|
@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{59D8D1A8-D9F
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.Core", "src\Interlinked.Core\Interlinked.Core.csproj", "{21F768D4-54BA-4B55-8A03-399F697394FD}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.Core", "src\Interlinked.Core\Interlinked.Core.csproj", "{21F768D4-54BA-4B55-8A03-399F697394FD}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.Locator", "src\Interlinked.Locator\Interlinked.Locator.csproj", "{069E0BCC-F019-4EDB-83F2-698EE5C33825}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.Shared", "src\Interlinked.Shared\Interlinked.Shared.csproj", "{0EF4026C-3675-4BBB-B352-26FBB7C96FA3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.Shared", "src\Interlinked.Shared\Interlinked.Shared.csproj", "{0EF4026C-3675-4BBB-B352-26FBB7C96FA3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.User", "src\Interlinked.User\Interlinked.User.csproj", "{5B94DC1F-170E-458C-BC5B-513C5ADB3F0A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interlinked.User", "src\Interlinked.User\Interlinked.User.csproj", "{5B94DC1F-170E-458C-BC5B-513C5ADB3F0A}"
|
||||||
|
@ -49,7 +47,6 @@ Global
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{21F768D4-54BA-4B55-8A03-399F697394FD} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
{21F768D4-54BA-4B55-8A03-399F697394FD} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
||||||
{069E0BCC-F019-4EDB-83F2-698EE5C33825} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
|
||||||
{0EF4026C-3675-4BBB-B352-26FBB7C96FA3} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
{0EF4026C-3675-4BBB-B352-26FBB7C96FA3} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
||||||
{5B94DC1F-170E-458C-BC5B-513C5ADB3F0A} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
{5B94DC1F-170E-458C-BC5B-513C5ADB3F0A} = {59D8D1A8-D9F0-464D-8654-D513FDC1A17F}
|
||||||
{A8E7D5AA-DB9D-44CC-B2A0-767C9D568B96} = {3A0C257D-5E1E-4FCE-A55D-8990F6F2F6B0}
|
{A8E7D5AA-DB9D-44CC-B2A0-767C9D568B96} = {3A0C257D-5E1E-4FCE-A55D-8990F6F2F6B0}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Interlinked.Locator.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController : ControllerBase
|
|
||||||
{
|
|
||||||
private static readonly string[] Summaries = new[]
|
|
||||||
{
|
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ILogger<WeatherForecastController> _logger;
|
|
||||||
|
|
||||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
public IEnumerable<WeatherForecast> Get()
|
|
||||||
{
|
|
||||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
{
|
|
||||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.11" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,25 +0,0 @@
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
// Add services to the container.
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
app.Run();
|
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:57682",
|
|
||||||
"sslPort": 44326
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"applicationUrl": "http://localhost:5202",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"applicationUrl": "https://localhost:7210;http://localhost:5202",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace Interlinked.Locator;
|
|
||||||
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
namespace Interlinked.Shared;
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
24
src/Interlinked.Shared/Model/AuthSession.cs
Normal file
24
src/Interlinked.Shared/Model/AuthSession.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Interlinked.Shared.Model;
|
||||||
|
|
||||||
|
public class AuthSession
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public required byte[] Key { get; set; }
|
||||||
|
public required byte[] Secret { get; set; }
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public IPAddress LastAddress { get; set; } = default!;
|
||||||
|
public UserModel User { get; set; } = default!;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
//TODO: Use key
|
||||||
|
using var sha = SHA256.Create();
|
||||||
|
var hash = sha.ComputeHash(Secret);
|
||||||
|
return $"{Convert.ToBase64String(Key)}.{Convert.ToBase64String(hash)}";
|
||||||
|
//Validate
|
||||||
|
}
|
||||||
|
}
|
22
src/Interlinked.Shared/Model/Project.cs
Normal file
22
src/Interlinked.Shared/Model/Project.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Interlinked.Shared.Model;
|
||||||
|
|
||||||
|
public class Project
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public long OwnerId { get; set; }
|
||||||
|
public virtual UserModel Owner { get; set; } = default!;
|
||||||
|
public ICollection<Tag> Tags { get; set; } = default!;
|
||||||
|
|
||||||
|
}
|
17
src/Interlinked.Shared/Model/Tag.cs
Normal file
17
src/Interlinked.Shared/Model/Tag.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Interlinked.Shared.Model;
|
||||||
|
|
||||||
|
public class Tag
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public required string Identifier { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<UserModel> Users { get; set; } = default!;
|
||||||
|
public virtual ICollection<Project> Projects { get; set; } = default!;
|
||||||
|
}
|
48
src/Interlinked.Shared/Model/User.cs
Normal file
48
src/Interlinked.Shared/Model/User.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Interlinked.Shared.Model;
|
||||||
|
|
||||||
|
public class UserModel
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public long Id { get; set; }
|
||||||
|
[MaxLength(32)]
|
||||||
|
public required string Username { get; set; }
|
||||||
|
[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; }
|
||||||
|
|
||||||
|
public static byte[] HashPassword(string password, byte[] salt)
|
||||||
|
{
|
||||||
|
return Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, 1000, HashAlgorithmName.SHA256, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] HashPassword(string password) => HashPassword(password, Salt);
|
||||||
|
|
||||||
|
public bool CheckHash(string password) => HashPassword(password).SequenceEqual(Hash);
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual ICollection<AuthSession> AuthSessions { get; set; } = default!;
|
||||||
|
}
|
86
src/Interlinked.User/Controllers/AuthController.cs
Normal file
86
src/Interlinked.User/Controllers/AuthController.cs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Interlinked.Shared.Model;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Interlinked.User.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/auth")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
public AuthController(StoreContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
private readonly StoreContext _db;
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("register")]
|
||||||
|
public async Task<IActionResult> Register([FromBody] string email, [FromBody] string username, [FromBody] string? name, [FromBody] string password)
|
||||||
|
{
|
||||||
|
email = email.ToLower().Trim();
|
||||||
|
username = username.Trim();
|
||||||
|
|
||||||
|
if (_db.Users.AsNoTracking().Any(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return Conflict("Username taken.");
|
||||||
|
}
|
||||||
|
//VALID???
|
||||||
|
if (_db.Users.AsNoTracking().Any(u => u.Email == email))
|
||||||
|
{
|
||||||
|
return Conflict("Email taken.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] salt = new byte[16];
|
||||||
|
RandomNumberGenerator.Fill(salt);
|
||||||
|
byte[] hash = UserModel.HashPassword(password, salt.ToArray());
|
||||||
|
|
||||||
|
UserModel u = new()
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
Username = username,
|
||||||
|
Salt = salt,
|
||||||
|
Name = name,
|
||||||
|
Hash = hash
|
||||||
|
};
|
||||||
|
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
await _db.Users.AddAsync(u);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<IActionResult> Login([FromBody] string email, [FromBody] string password)
|
||||||
|
{
|
||||||
|
email = email.ToLower().Trim();
|
||||||
|
UserModel? u = _db.Users.AsNoTracking().FirstOrDefault(u => email.Equals(u.Email));
|
||||||
|
|
||||||
|
if (u is null || !u.CheckHash(password))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Create auth session
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("logout")]
|
||||||
|
public async Task<IActionResult> Logout()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Interlinked.User.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController : ControllerBase
|
|
||||||
{
|
|
||||||
private static readonly string[] Summaries = new[]
|
|
||||||
{
|
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ILogger<WeatherForecastController> _logger;
|
|
||||||
|
|
||||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
public IEnumerable<WeatherForecast> Get()
|
|
||||||
{
|
|
||||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
{
|
|
||||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,8 +7,16 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<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.AspNetCore.OpenApi" Version="7.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Interlinked.Shared\Interlinked.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
|
using Interlinked.Shared.Model;
|
||||||
|
using Interlinked.User;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
builder.Services.AddDbContext<StoreContext>();
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
builder.Services.AddMemoryCache();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddDefaultIdentity<UserModel>(o => o.SignIn.RequireConfirmedAccount = true)
|
||||||
builder.Services.AddSwaggerGen();
|
.AddEntityFrameworkStores<StoreContext>();
|
||||||
|
builder.Services.ConfigureApplicationCookie(options =>
|
||||||
|
{
|
||||||
|
// Cookie settings
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
|
options.LoginPath = "/auth/login";
|
||||||
|
options.AccessDeniedPath = "/auth/account/uwuless";
|
||||||
|
options.SlidingExpiration = true;
|
||||||
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
75
src/Interlinked.User/Services/AuthManager.cs
Normal file
75
src/Interlinked.User/Services/AuthManager.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Interlinked.Shared.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace Interlinked.User.Services;
|
||||||
|
|
||||||
|
public class AuthManager
|
||||||
|
{
|
||||||
|
public const string AUTH_COOKIE_NAME = "bOiKissEr_ᓚᘏᗢ_UwU";
|
||||||
|
public AuthManager(StoreContext db, HttpContext http, IMemoryCache cache)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_http = http;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly StoreContext _db;
|
||||||
|
private readonly HttpContext _http;
|
||||||
|
|
||||||
|
public async Task<AuthSession> CreateSession(UserModel user)
|
||||||
|
{
|
||||||
|
byte[] secret = new byte[16], key = new byte[16];
|
||||||
|
RandomNumberGenerator.Fill(secret);
|
||||||
|
RandomNumberGenerator.Fill(key);
|
||||||
|
AuthSession sess = new()
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
Secret = secret,
|
||||||
|
Key = key
|
||||||
|
};
|
||||||
|
|
||||||
|
await _db.AuthSessions.AddAsync(sess);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
_http.Response.Cookies.Append(AUTH_COOKIE_NAME, sess.ToString());
|
||||||
|
return sess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RevokeSession(AuthSession sess)
|
||||||
|
{
|
||||||
|
_db.AuthSessions.Remove(sess);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthSession?> RetrieveCookie()
|
||||||
|
{
|
||||||
|
if (!_http.Request.Cookies.TryGetValue(AUTH_COOKIE_NAME, out string? val) || val is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = val.IndexOf('.');
|
||||||
|
string keyStr = val[0..(idx - 1)], hashStr = val;
|
||||||
|
byte[] key = Convert.FromBase64String(keyStr), hash = Convert.FromBase64String(hashStr);
|
||||||
|
|
||||||
|
var sess = await _db.AuthSessions.SingleOrDefaultAsync(s => s.Key.SequenceEqual(key));
|
||||||
|
|
||||||
|
if (sess is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var sha = SHA256.Create();
|
||||||
|
var trueHash = sha.ComputeHash(sess.Secret);
|
||||||
|
|
||||||
|
return trueHash.SequenceEqual(hash) ? sess : null;
|
||||||
|
}
|
||||||
|
}
|
19
src/Interlinked.User/StoreContext.cs
Normal file
19
src/Interlinked.User/StoreContext.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using Interlinked.Shared.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Interlinked.User;
|
||||||
|
|
||||||
|
public class StoreContext : DbContext
|
||||||
|
{
|
||||||
|
public DbSet<UserModel> Users => Set<UserModel>();
|
||||||
|
public DbSet<Project> Projects => Set<Project>();
|
||||||
|
public DbSet<Tag> Tags => Set<Tag>();
|
||||||
|
public DbSet<AuthSession> AuthSessions => Set<AuthSession>();
|
||||||
|
|
||||||
|
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<Project>().HasMany(p => p.Tags).WithMany(t => t.Projects);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue