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
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:
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user