mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-06 15:46:03 +02:00
docs: Update documentation for 145 files
Generated by AurionDocs
Job ID: 934f8c39-8082-4942-8d17-72ed8f5f8d50
Source commit: 40aea9a
This commit is contained in:
@@ -8,68 +8,31 @@
|
||||
```mermaid
|
||||
%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%%
|
||||
flowchart TB
|
||||
Start["Start RegisterUserAsync"]
|
||||
Start --> CheckEmpty
|
||||
CheckEmpty["Check username and password not empty"]
|
||||
CheckEmpty -->|"missing"| FailMissing
|
||||
CheckEmpty -->|"present"| CheckUsernameRegex
|
||||
|
||||
FailMissing["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;Username and password are required.#quot;)"]
|
||||
|
||||
CheckUsernameRegex["Validate username with ValidationConstants.UsernameRegex()"]
|
||||
CheckUsernameRegex -->|"invalid"| FailUsernameRegex
|
||||
CheckUsernameRegex -->|"valid"| CheckPwdMin
|
||||
|
||||
FailUsernameRegex["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;Username must be 3-50 characters and contain only letters, digits, underscores, or hyphens.#quot;)"]
|
||||
|
||||
CheckPwdMin["Check password length >= 6"]
|
||||
CheckPwdMin -->|"too short"| FailPwdShort
|
||||
CheckPwdMin -->|"ok"| CheckPwdMax
|
||||
|
||||
FailPwdShort["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;Password must be at least 6 characters.#quot;)"]
|
||||
|
||||
CheckPwdMax["Check password length <= ValidationConstants.MaxPasswordLength"]
|
||||
CheckPwdMax -->|"too long"| FailPwdLong
|
||||
CheckPwdMax -->|"ok"| Normalize
|
||||
|
||||
FailPwdLong["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;Password must not exceed ValidationConstants.MaxPasswordLength characters.#quot;)"]
|
||||
|
||||
Normalize["Normalize username (ToLowerInvariant and Trim)"]
|
||||
Normalize --> CheckReserved
|
||||
|
||||
CheckReserved["Compare normalized username to UsersController.DeletedUserName"]
|
||||
CheckReserved -->|"reserved"| FailReserved
|
||||
CheckReserved -->|"not reserved"| CreateScope
|
||||
|
||||
FailReserved["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;This username is reserved.#quot;)"]
|
||||
|
||||
CreateScope["Create scope and get EchoHubDbContext from _scopeFactory"]
|
||||
CreateScope --> CheckExists
|
||||
|
||||
CheckExists["Check if db.Users.AnyAsync(u => u.Username == normalizedUsername)"]
|
||||
CheckExists -->|"exists"| FailAlreadyExists
|
||||
CheckExists -->|"not exists"| CheckIsFirstUser
|
||||
|
||||
FailAlreadyExists["Return UserOperationResult.Fail(UserError.AlreadyExists, #quot;Username is already taken.#quot;)"]
|
||||
|
||||
CheckIsFirstUser["Determine isFirstUser = !await db.Users.AnyAsync()"]
|
||||
CheckIsFirstUser -->|"first user"| CreateUser
|
||||
CheckIsFirstUser -->|"not first"| RegistrationGate
|
||||
|
||||
RegistrationGate["Check RegistrationMode (open / invite / closed)"]
|
||||
RegistrationGate -->|"closed"| FailRegistrationClosed
|
||||
RegistrationGate -->|"invite"| TryInvite
|
||||
RegistrationGate -->|"open"| CreateUser
|
||||
|
||||
FailRegistrationClosed["Return UserOperationResult.Fail(UserError.ValidationFailed, #quot;Registration is closed on this server.#quot;)"]
|
||||
|
||||
TryInvite["Call TryConsumeInviteAsync(db, inviteCode)"]
|
||||
TryInvite -->|"invite error"| FailInviteError
|
||||
TryInvite -->|"ok"| CreateUser
|
||||
|
||||
FailInviteError["Return UserOperationResult.Fail(UserError.ValidationFailed, inviteError)"]
|
||||
|
||||
CreateUser["Create new User instance (Id = Guid.NewGuid(), set fields)"]
|
||||
start["Start RegisterUserAsync"]
|
||||
start --> checkEmpty["Check username and password not empty"]
|
||||
checkEmpty -->|"invalid"| emptyFail["Return UserOperationResult.Fail(UserError.ValidationFailed): Username and password required"]
|
||||
checkEmpty -->|"valid"| regexCheck["Validate username with ValidationConstants.UsernameRegex()"]
|
||||
regexCheck -->|"invalid"| regexFail["Return UserOperationResult.Fail(UserError.ValidationFailed): Username format invalid"]
|
||||
regexCheck -->|"valid"| pwMinCheck["Check password length >= 6"]
|
||||
pwMinCheck -->|"no"| pwMinFail["Return UserOperationResult.Fail(UserError.ValidationFailed): Password must be at least 6 characters"]
|
||||
pwMinCheck -->|"yes"| pwMaxCheck["Check password length <= ValidationConstants.MaxPasswordLength"]
|
||||
pwMaxCheck -->|"no"| pwMaxFail["Return UserOperationResult.Fail(UserError.ValidationFailed): Password exceeds max length"]
|
||||
pwMaxCheck -->|"yes"| normalize["Normalize username (ToLowerInvariant().Trim())"]
|
||||
normalize --> reservedCheck["If normalized == UsersController.DeletedUserName"]
|
||||
reservedCheck -->|"yes"| reservedFail["Return UserOperationResult.Fail(UserError.ValidationFailed): This username is reserved"]
|
||||
reservedCheck -->|"no"| dbScope["Create scope and get EchoHubDbContext"]
|
||||
dbScope --> existsCheck["If EchoHubDbContext.Users.Any(u => u.Username == normalized)"]
|
||||
existsCheck -->|"yes"| existsFail["Return UserOperationResult.Fail(UserError.AlreadyExists): Username is already taken"]
|
||||
existsCheck -->|"no"| isFirstCheck["Determine isFirstUser = !EchoHubDbContext.Users.Any()"]
|
||||
isFirstCheck -->|"true"| createUser["Create new User entity (new User { ... })"]
|
||||
isFirstCheck -->|"false"| regMode["Check UserService.RegistrationMode (open, invite, closed)"]
|
||||
regMode -->|"closed"| closedFail["Return UserOperationResult.Fail(UserError.ValidationFailed): Registration is closed on this server"]
|
||||
regMode -->|"invite"| inviteTry["Call TryConsumeInviteAsync(EchoHubDbContext, inviteCode)"]
|
||||
inviteTry -->|"error"| inviteFail["Return UserOperationResult.Fail(UserError.ValidationFailed): invite error returned"]
|
||||
inviteTry -->|"ok"| createUser
|
||||
regMode -->|"open"| createUser
|
||||
createUser --> save["Save new User to EchoHubDbContext and assign roles (ServerRole may apply)"]
|
||||
save --> success["Return UserOperationResult.Success(UserProfileDto)"]
|
||||
```
|
||||
|
||||
```csharp
|
||||
@@ -77,28 +40,27 @@ public class UserService : IUserService
|
||||
```
|
||||
|
||||
|
||||
Implements user-account operations for the server, most notably account registration. Use this concrete IUserService implementation when you need the server-backed behavior: configuration-driven registration modes (open / invite / closed), automatic owner bootstrap for the very first account, username/password validation, reserved-name checks, and password hashing before persisting users.
|
||||
Handles user registration and related user-management concerns for the server. Use `UserService` when you need a high-level operation that validates credentials, enforces server-wide registration policy, creates the initial server owner account, hashes passwords, and returns canonical [`UserOperationResult`](../../EchoHub.Core/DTOs/CommonDtos.cs.md) responses instead of interacting with [`EchoHubDbContext`](../Data/EchoHubDbContext.cs.md) directly.
|
||||
|
||||
## Remarks
|
||||
UserService is the server-side implementation of IUserService and is responsible for safe, policy-driven user creation. It reads the registration policy from IConfiguration (Server:Registration), uses an IServiceScopeFactory to create a scoped EchoHubDbContext per operation (so the service can be used from different DI lifetimes), and enforces validation rules from ValidationConstants. The very first account created on a fresh database is always promoted to ServerRole.Owner to allow bootstrapping an administration account. Invite consumption (when registration is in "invite" mode) is performed via an atomic/guarded update to avoid races when two registrations attempt to use the last invite simultaneously.
|
||||
`UserService` encapsulates the rules and side effects required to create a new [`User`](../../EchoHub.Core/Models/User.cs.md) in the application: it validates the `username` with `ValidationConstants.UsernameRegex()`, enforces password length (minimum 6 characters and a maximum of `ValidationConstants.MaxPasswordLength`), normalizes the username to lowercase and trimmed form, prevents use of the reserved `Controllers.UsersController.DeletedUserName`, and checks uniqueness using [`EchoHubDbContext`](../Data/EchoHubDbContext.cs.md). The `RegistrationMode` property reads `Server:Registration` from `IConfiguration` and controls whether new sign-ups are allowed (`"open"`), require a valid invite (`"invite"`), or are disallowed (`"closed"`). The very first account created on an empty database is automatically assigned `ServerRole.Owner` to allow server bootstrap. For invite-based registration, `UserService` defers to the private `TryConsumeInviteAsync` routine which (per its comment) performs a guarded update so concurrent registrations cannot both consume the same invite.
|
||||
|
||||
## Example
|
||||
```csharp
|
||||
// Typical usage from an async context where `userService` is resolved from DI
|
||||
var result = await userService.RegisterUserAsync("alice", "s3cretP@ss", displayName: "Alice");
|
||||
// Given an IUserService instance (e.g. resolved from DI):
|
||||
var result = await userService.RegisterUserAsync("alice", "s3cret!", displayName: "Alice");
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var profile = result; // UserOperationResult.Success wraps the created UserProfileDto
|
||||
// proceed with signed-in flow
|
||||
var profile = result; // result carries the created [`UserProfileDto`](../../EchoHub.Core/DTOs/ProfileDtos.cs.md) via `UserOperationResult.Success`
|
||||
// proceed with login or return profile to caller
|
||||
}
|
||||
else
|
||||
{
|
||||
// registration failed; map user-visible error to response
|
||||
// handle failure: message and [`UserError`](../../EchoHub.Core/DTOs/CommonDtos.cs.md) are available from the [`UserOperationResult`](../../EchoHub.Core/DTOs/CommonDtos.cs.md) returned
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Username handling: the service normalizes usernames by trimming and lower-casing; a specific reserved name (UsersController.DeletedUserName) is rejected.
|
||||
- The first user bypasses the registration gate and becomes ServerRole.Owner — this is intentional so a fresh server can be bootstrapped.
|
||||
- Passwords are hashed using BCrypt.Net.BCrypt.HashPassword before being stored; there is no exposed mechanism here to change the hash algorithm.
|
||||
- Invite consumption uses a guarded database update to prevent two concurrent registrations from both consuming the last available use of a code; if invite validation fails, RegisterUserAsync returns a validation failure with the invite error message.
|
||||
- The `RegistrationMode` is computed on each access from `IConfiguration["Server:Registration"]`; changing that configuration at runtime affects subsequent calls to `RegisterUserAsync` immediately.
|
||||
- The first created user bypasses invite/closed checks and is assigned `ServerRole.Owner`; this is intentional to allow initial server bootstrap and means the first successful registration must be protected in deployment scenarios.
|
||||
- `RegisterUserAsync` normalizes usernames to lowercase and trims them before uniqueness checks, so the system enforces case-insensitive username uniqueness. Passwords are hashed with `BCrypt.Net.BCrypt.HashPassword` before being stored.
|
||||
Reference in New Issue
Block a user