Class AccountService
Account service providing async operations for account creation and login. Uses EF Core compiled queries for hot paths and the BaseService execution strategy for retries. Returns DatabaseResult for consistent, safe error handling with sanitized messages.
public sealed class AccountService : BaseService<AccountEntity>, IAccountService, IExistsByKeyAction<string>
- Inheritance
-
AccountService
- Implements
- Inherited Members
Constructors
AccountService(INpgsqlDbContextFactory)
Initializes a new instance of AccountService.
public AccountService(INpgsqlDbContextFactory dbContextFactory)
Parameters
dbContextFactoryINpgsqlDbContextFactoryDbContext factory for creating contexts.
Exceptions
- ArgumentNullException
Thrown when dbContextFactory is null.
Methods
ClearTotpAsync(string, CancellationToken)
Clears all TOTP fields when a user disables 2FA. Atomically resets totp_secret, totp_enabled, totp_verified_at, and last_totp_window. Recovery codes should be invalidated separately via ITwoFactorRecoveryCodeService.
public Task<DatabaseResult> ClearTotpAsync(string accountName, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
ExistsAsync(string, CancellationToken)
Checks whether an entity exists for the given key.
public Task<DatabaseResult<bool>> ExistsAsync(string accountName, CancellationToken cancellationToken = default)
Parameters
accountNamestringcancellationTokenCancellationTokenToken to cancel the operation.
Returns
FetchByDiscordLinkCodeAsync(string, CancellationToken)
Fetches an account by its Discord link code for verification. Used by the Discord bot to confirm in-game verification.
public Task<DatabaseResult<AccountData?>> FetchByDiscordLinkCodeAsync(string linkCode, CancellationToken cancellationToken = default)
Parameters
linkCodestringThe Discord link code to search for.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult<AccountData?>>
DatabaseResult containing the AccountData if found, or null.
FetchForLoginAsync(string, bool, CancellationToken)
Retrieves account authentication data for login.
public Task<DatabaseResult<AccountData>> FetchForLoginAsync(string username, bool email = false, CancellationToken cancellationToken = default)
Parameters
usernamestringThe account name or email to query, depending on
email.emailboolWhen false,
usernameis treated as the account name (3-32 chars). When true, it is treated as an email (max 320 chars).cancellationTokenCancellationTokenToken to cancel the asynchronous operation.
Returns
- Task<DatabaseResult<AccountData>>
DatabaseResult containing AccountData with authentication credentials on success, or error information on failure.
Remarks
This method uses LINQ query which automatically benefits from EF Core's configured retry policy for transient failures.
Success: Returns AccountData with salt, verifier, access level, and timestamps.
- (Banned, null): Account exists but is banned Success: Returns AccountData with salt, verifier, access level, and timestamps. Failure cases:
- VALIDATION_ERROR: Username or email validation failed
- DB_NOT_FOUND: Account does not exist
- ACCOUNT_BANNED: Account exists but is banned
- DB_CONNECTION_FAILED: Database connection error (transient)
- DB_TIMEOUT: Query timeout (transient)
Security Note: Does not distinguish between non-existent accounts and banned accounts in error messages to prevent username enumeration attacks.
The returned AccountData DTO is a defensive copy and can be safely used after the database context is disposed.
FetchLastLoginAsync(string, bool, CancellationToken)
Gets the last login time for an account.
public Task<DatabaseResult<DateTime>> FetchLastLoginAsync(string username, bool email = false, CancellationToken cancellationToken = default)
Parameters
usernamestringThe account name or email to query, depending on
email.emailboolWhen false,
usernameis treated as the account name (3-32 chars). When true, it is treated as an email (max 320 chars).cancellationTokenCancellationTokenToken to cancel the asynchronous operation.
Returns
- Task<DatabaseResult<DateTime>>
DatabaseResult containing the last login timestamp on success, or error information on failure.
Remarks
This method uses LINQ query which automatically benefits from EF Core's configured retry policy for transient failures.
Success: Returns the last login timestamp. Failure cases:
- VALIDATION_ERROR: Username or email validation failed
- DB_NOT_FOUND: Account does not exist
- DB_CONNECTION_FAILED: Database connection error (transient)
- DB_TIMEOUT: Query timeout (transient)
PersistAgeAsync(string, int, CancellationToken)
Updates the age for an account.
public Task<DatabaseResult> PersistAgeAsync(string accountName, int age, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
ageintThe age value.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistAsync(string, string, string, string, int, CancellationToken)
Creates a new account with the specified credentials.
public Task<DatabaseResult> PersistAsync(string accountName, string salt, string verifier, string email, int age, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name. Must be 3-32 characters.
saltstringThe salt for SRP password hashing. Must not be null or whitespace.
verifierstringThe verifier for SRP password hashing. Must not be null or whitespace.
emailstringThe account email. Must not be empty and must not exceed 320 characters.
ageintThe account holder age. Must be between 0 and 200.
cancellationTokenCancellationTokenToken to cancel the asynchronous operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure with error details.
Remarks
Success: Account created with Player access level and current timestamp. Failure cases:
- VALIDATION_ERROR: Invalid username, salt, verifier, email, or age
- UNIQUE_VIOLATION: Account name already exists (non-transient)
- DATABASE_ERROR: Unexpected database error
PersistAutoVerifiedAsync(string, CancellationToken)
Marks an account verified without requiring a verification code, clearing any pending code in the process.
This is the server-initiated counterpart to PersistVerifiedAsync(string, int, CancellationToken) and
exists for the development-only AutoVerifyAccounts path, where no code is
ever generated or emailed. It performs no code check, so it must never be reachable
from a client-supplied value — client-driven verification goes through
PersistVerifiedAsync(string, int, CancellationToken), which validates the code atomically.
public Task<DatabaseResult> PersistAutoVerifiedAsync(string accountName, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistDiscordLinkCodeAsync(string, string?, CancellationToken)
Sets the temporary Discord link verification code for an account. The Discord bot generates this code; the user verifies in-game with /verify.
public Task<DatabaseResult> PersistDiscordLinkCodeAsync(string accountName, string? linkCode, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
linkCodestringThe link code, or null to clear after verification.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistEmailAsync(string, string?, CancellationToken)
Updates the email address for an account.
public Task<DatabaseResult> PersistEmailAsync(string accountName, string? email, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
emailstringThe new email address, or null to clear.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistLastLoginAsync(string, CancellationToken)
Updates the last login timestamp for an account atomically. Uses execution strategy for automatic retry on transient failures.
public Task<DatabaseResult> PersistLastLoginAsync(string accountName, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name. Must be 3-32 characters.
cancellationTokenCancellationTokenToken to cancel the asynchronous operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure with error details.
Remarks
Uses atomic UPDATE without loading entity to prevent race conditions. Wrapped in execution strategy for automatic retry on transient failures.
Success: Last login timestamp updated to current database server time. Failure cases:
- VALIDATION_ERROR: Invalid username (length or format)
- DB_NOT_FOUND: Account does not exist
- DB_CONNECTION_FAILED: Database connection error (transient)
- DB_TIMEOUT: Operation timeout (transient)
- DB_QUERY_FAILED: Unexpected database error
PersistLastTotpWindowAsync(string, long, CancellationToken)
Updates the last TOTP time-step window used for an account, preventing replay attacks. Only updates if the new window is greater than the stored value.
public Task<DatabaseResult> PersistLastTotpWindowAsync(string accountName, long totpWindow, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
totpWindowlongThe time-step window of the verified code.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistTotpEnabledAsync(string, bool, CancellationToken)
Enables or disables TOTP two-factor authentication for an account.
public Task<DatabaseResult> PersistTotpEnabledAsync(string accountName, bool enabled, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
enabledboolWhether TOTP should be enabled.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistTotpSecretAsync(string, string, CancellationToken)
Stores the encrypted TOTP secret and enables TOTP for an account. Called during 2FA enrollment after the server generates the secret.
public Task<DatabaseResult> PersistTotpSecretAsync(string accountName, string encryptedTotpSecret, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
encryptedTotpSecretstringThe Base32-encoded TOTP secret, encrypted at rest by the server.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistTotpVerifiedAtAsync(string, long, CancellationToken)
Records the first successful TOTP verification timestamp, confirming 2FA setup. Atomically sets totp_verified_at and updates last_totp_window.
public Task<DatabaseResult> PersistTotpVerifiedAtAsync(string accountName, long totpWindow, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
totpWindowlongThe time-step window of the verified code.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistVerificationEmailSentAsync(string, CancellationToken)
Records that the verification email has been successfully sent via SMTP. Once set, login is blocked for unverified accounts until the user provides the correct verify code.
public Task<DatabaseResult> PersistVerificationEmailSentAsync(string accountName, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistVerifiedAsync(string, int, CancellationToken)
Sets the verified status for an account. Called when the user provides the correct verify code from the email verification link.
public Task<DatabaseResult> PersistVerifiedAsync(string accountName, int verifyCode, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
verifyCodeintThe verification code the user provided. Must match the stored verify code.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.
PersistVerifyCodeAsync(string, int, DateTime, CancellationToken)
Sets the verification code for an account, along with the UTC expiry timestamp after which the code is no longer redeemable.
public Task<DatabaseResult> PersistVerifyCodeAsync(string accountName, int verifyCode, DateTime expiresUtc, CancellationToken cancellationToken = default)
Parameters
accountNamestringThe account name.
verifyCodeintThe randomly generated verification code.
expiresUtcDateTimeUTC instant after which the code is invalid.
cancellationTokenCancellationTokenToken to cancel the operation.
Returns
- Task<DatabaseResult>
DatabaseResult indicating success or failure.