🎉 Initial Commit

This commit is contained in:
2025-04-08 23:26:23 +08:00
commit c39fdceeb6
17 changed files with 889 additions and 0 deletions

View File

@ -0,0 +1,60 @@
using System.ComponentModel.DataAnnotations;
using System.Text;
using NodaTime;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Security;
namespace DysonNetwork.Sphere.Account;
public class Account : BaseModel
{
public long Id { get; set; }
[MaxLength(256)] public string Name { get; set; } = string.Empty;
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
public ICollection<AccountContact> Contacts { get; set; } = new List<AccountContact>();
public ICollection<AccountAuthFactor> AuthFactors { get; set; } = new List<AccountAuthFactor>();
}
public class AccountContact : BaseModel
{
public long Id { get; set; }
public AccountContactType Type { get; set; }
public Instant? VerifiedAt { get; set; }
[MaxLength(1024)] public string Content { get; set; } = string.Empty;
public Account Account { get; set; } = null!;
}
public enum AccountContactType
{
Email, PhoneNumber, Address
}
public class AccountAuthFactor : BaseModel
{
public long Id { get; set; }
public AccountAuthFactorType Type { get; set; }
public string? Secret { get; set; } = null;
public Account Account { get; set; } = null!;
public AccountAuthFactor HashSecret(int cost = 12)
{
if(Secret == null) return this;
var passwordBytes = Encoding.UTF8.GetBytes(Secret);
var random = new SecureRandom();
var salt = new byte[16];
random.NextBytes(salt);
var hashed = BCrypt.Generate(passwordBytes, salt, cost);
Secret = Convert.ToBase64String(hashed);
return this;
}
}
public enum AccountAuthFactorType
{
Password, EmailCode, InAppCode, TimedCode
}

View File

@ -0,0 +1,57 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace DysonNetwork.Sphere.Account;
[ApiController]
[Route("/accounts")]
public class AccountController(AppDatabase db)
{
[HttpGet("{name}")]
[ProducesResponseType<Account>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Account?>> GetByName(string name)
{
var account = await db.Accounts.FindAsync(name);
return account;
}
public class AccountCreateRequest
{
[Required] [MaxLength(256)] public string Name { get; set; } = string.Empty;
[Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty;
[Required] [MaxLength(1024)] public string Email { get; set; } = string.Empty;
[Required] [MinLength(4)] [MaxLength(128)] public string Password { get; set; } = string.Empty;
}
[HttpPost]
[ProducesResponseType<Account>(StatusCodes.Status200OK)]
public async Task<ActionResult<Account>> CreateAccount([FromBody] AccountCreateRequest request)
{
var account = new Account
{
Name = request.Name,
Nick = request.Nick,
Contacts = new List<AccountContact>()
{
new AccountContact
{
Type = AccountContactType.Email,
Content = request.Email
}
},
AuthFactors = new List<AccountAuthFactor>
{
new AccountAuthFactor
{
Type = AccountAuthFactorType.Password,
Secret = request.Password
}.HashSecret()
}
};
await db.Accounts.AddAsync(account);
await db.SaveChangesAsync();
return account;
}
}