Harden public account registration
Deploy Promiscuity Auth API / deploy (push) Successful in 1m13s
Deploy Promiscuity Character API / deploy (push) Successful in 59s
Deploy Promiscuity Crafting API / deploy (push) Successful in 58s
Deploy Promiscuity Inventory API / deploy (push) Successful in 58s
Deploy Promiscuity Locations API / deploy (push) Successful in 59s
Deploy Promiscuity Mail API / deploy (push) Successful in 59s
Deploy Promiscuity World API / deploy (push) Successful in 59s
k8s smoke test / test (push) Successful in 20s

This commit is contained in:
2026-07-11 12:39:02 -05:00
parent 7f21b40d93
commit 7faef2e976
4 changed files with 34 additions and 12 deletions
@@ -3,9 +3,12 @@ using AuthApi.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Mail;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace AuthApi.Controllers; namespace AuthApi.Controllers;
@@ -25,15 +28,31 @@ public class AuthController : ControllerBase
[HttpPost("register")] [HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequest req) public async Task<IActionResult> Register([FromBody] RegisterRequest req)
{ {
if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password)) var username = req.Username?.Trim() ?? "";
return BadRequest("Username and password required"); var password = req.Password ?? "";
var email = req.Email?.Trim() ?? "";
if (await _users.GetByUsernameAsync(req.Username) != null) if (!Regex.IsMatch(username, "^[A-Za-z0-9_]{3,24}$"))
return BadRequest("User already exists"); return BadRequest("Username must be 3-24 characters using only letters, numbers, or underscores");
if (password.Length < 8)
return BadRequest("Password must be at least 8 characters");
var emailDomain = email.Split('@').LastOrDefault() ?? "";
if (string.IsNullOrWhiteSpace(email) || !MailAddress.TryCreate(email, out _) || !emailDomain.Contains('.'))
return BadRequest("A valid email address is required");
var hash = BCrypt.Net.BCrypt.HashPassword(req.Password); if (await _users.GetByUsernameAsync(username) != null)
var user = new User { Username = req.Username, PasswordHash = hash, Role = "USER", Email = req.Email }; return Conflict("Username already exists");
var hash = BCrypt.Net.BCrypt.HashPassword(password);
var user = new User { Username = username, PasswordHash = hash, Role = "USER", Email = email };
try
{
await _users.CreateAsync(user); await _users.CreateAsync(user);
}
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
return Conflict("Username already exists");
}
return Ok("User created"); return Ok("User created");
} }
+5 -2
View File
@@ -9,9 +9,12 @@ Inbound JSON documents
{ {
"username": "string", "username": "string",
"password": "string", "password": "string",
"email": "string (optional)" "email": "string"
} }
``` ```
Usernames must be 3-24 characters containing only letters, numbers, or
underscores. Passwords must contain at least 8 characters, and email is
required with a valid address format.
- LoginRequest (`POST /api/auth/login`) - LoginRequest (`POST /api/auth/login`)
```json ```json
{ {
@@ -42,7 +45,7 @@ Stored documents (MongoDB)
"username": "string", "username": "string",
"passwordHash": "string", "passwordHash": "string",
"role": "USER | SUPER", "role": "USER | SUPER",
"email": "string (optional)", "email": "string",
"refreshToken": "string (optional)", "refreshToken": "string (optional)",
"refreshTokenExpiry": "string (optional, ISO-8601 datetime)" "refreshTokenExpiry": "string (optional, ISO-8601 datetime)"
} }
+1 -1
View File
@@ -4,7 +4,7 @@
See `DOCUMENTS.md` for request payloads and stored document shapes. See `DOCUMENTS.md` for request payloads and stored document shapes.
## Endpoints ## Endpoints
- `POST /api/auth/register` Register a new user. - `POST /api/auth/register` Register a USER account with a username, email, and password.
- `POST /api/auth/login` Issue access and refresh tokens. - `POST /api/auth/login` Issue access and refresh tokens.
- `POST /api/auth/refresh` Refresh an access token. - `POST /api/auth/refresh` Refresh an access token.
- `POST /api/auth/logout` Revoke the current access token. - `POST /api/auth/logout` Revoke the current access token.
@@ -19,8 +19,8 @@ public class UserService
_col.Indexes.CreateOne(new CreateIndexModel<User>(keys, new CreateIndexOptions { Unique = true })); _col.Indexes.CreateOne(new CreateIndexModel<User>(keys, new CreateIndexOptions { Unique = true }));
} }
public Task<User?> GetByUsernameAsync(string username) => public async Task<User?> GetByUsernameAsync(string username) =>
_col.Find(u => u.Username == username).FirstOrDefaultAsync(); await _col.Find(u => u.Username == username).FirstOrDefaultAsync();
public Task CreateAsync(User user) => _col.InsertOneAsync(user); public Task CreateAsync(User user) => _col.InsertOneAsync(user);