Table of Contents

Class AccountService

Namespace
FishMMO.Database.Npgsql.Services
Assembly
FishMMO-DB.dll

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

dbContextFactory INpgsqlDbContextFactory

DbContext 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

accountName string

The account name.

cancellationToken CancellationToken

Token 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

accountName string
cancellationToken CancellationToken

Token to cancel the operation.

Returns

Task<DatabaseResult<bool>>

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

linkCode string

The Discord link code to search for.

cancellationToken CancellationToken

Token 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

username string

The account name or email to query, depending on email.

email bool

When false, username is treated as the account name (3-32 chars). When true, it is treated as an email (max 320 chars).

cancellationToken CancellationToken

Token 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

username string

The account name or email to query, depending on email.

email bool

When false, username is treated as the account name (3-32 chars). When true, it is treated as an email (max 320 chars).

cancellationToken CancellationToken

Token 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

accountName string

The account name.

age int

The age value.

cancellationToken CancellationToken

Token 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

accountName string

The account name. Must be 3-32 characters.

salt string

The salt for SRP password hashing. Must not be null or whitespace.

verifier string

The verifier for SRP password hashing. Must not be null or whitespace.

email string

The account email. Must not be empty and must not exceed 320 characters.

age int

The account holder age. Must be between 0 and 200.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

linkCode string

The link code, or null to clear after verification.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

email string

The new email address, or null to clear.

cancellationToken CancellationToken

Token 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

accountName string

The account name. Must be 3-32 characters.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

totpWindow long

The time-step window of the verified code.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

enabled bool

Whether TOTP should be enabled.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

encryptedTotpSecret string

The Base32-encoded TOTP secret, encrypted at rest by the server.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

totpWindow long

The time-step window of the verified code.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

verifyCode int

The verification code the user provided. Must match the stored verify code.

cancellationToken CancellationToken

Token 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

accountName string

The account name.

verifyCode int

The randomly generated verification code.

expiresUtc DateTime

UTC instant after which the code is invalid.

cancellationToken CancellationToken

Token to cancel the operation.

Returns

Task<DatabaseResult>

DatabaseResult indicating success or failure.