615b487a77
Adds missing <summary>/<param> XML docs across 99 server, worker, and test files so CommentChecker reports zero issues (TreatWarningsAsErrors needs the analyzer clean). Bundles in WIP dashboard work: NavSection extraction, MainLayout/site.css/js styling alignment, and DashboardOptions/Auth tweaks.
81 lines
3.1 KiB
C#
81 lines
3.1 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
|
|
|
/// <summary>
|
|
/// Factory for creating SQLite connections to the authentication store.
|
|
/// </summary>
|
|
public sealed class AuthSqliteConnectionFactory(IOptions<GatewayOptions> options)
|
|
{
|
|
/// <summary>
|
|
/// Busy timeout applied to every auth-store connection. SQLite retries a busy
|
|
/// database for this long before surfacing <c>SQLITE_BUSY</c>, so the concurrent
|
|
/// <c>MarkKeyUsedAsync</c> / audit-append writers degrade gracefully under load
|
|
/// instead of failing the request path.
|
|
/// </summary>
|
|
private static readonly TimeSpan BusyTimeout = TimeSpan.FromSeconds(5);
|
|
|
|
/// <summary>
|
|
/// Creates an unopened SQLite connection to the auth database. Prefer
|
|
/// <see cref="OpenConnectionAsync"/>, which also applies WAL journaling and the
|
|
/// busy timeout.
|
|
/// </summary>
|
|
public SqliteConnection CreateConnection()
|
|
{
|
|
string sqlitePath = options.Value.Authentication.SqlitePath;
|
|
string? directory = Path.GetDirectoryName(sqlitePath);
|
|
|
|
if (!string.IsNullOrWhiteSpace(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
SqliteConnectionStringBuilder builder = new()
|
|
{
|
|
DataSource = sqlitePath,
|
|
Mode = SqliteOpenMode.ReadWriteCreate,
|
|
Pooling = true,
|
|
DefaultTimeout = (int)BusyTimeout.TotalSeconds,
|
|
};
|
|
|
|
return new SqliteConnection(builder.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a SQLite connection, opens it, and configures WAL journaling and a
|
|
/// non-zero busy timeout so concurrent readers and writers degrade gracefully
|
|
/// rather than surfacing <c>SQLITE_BUSY</c> as a hard failure.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
|
/// <returns>An opened and configured SQLite connection.</returns>
|
|
public async Task<SqliteConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
|
{
|
|
SqliteConnection connection = CreateConnection();
|
|
try
|
|
{
|
|
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
await ConfigureConnectionAsync(connection, cancellationToken).ConfigureAwait(false);
|
|
return connection;
|
|
}
|
|
catch
|
|
{
|
|
await connection.DisposeAsync().ConfigureAwait(false);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static async Task ConfigureConnectionAsync(
|
|
SqliteConnection connection,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// WAL is a persistent, database-level setting; re-applying it per connection
|
|
// is cheap and a no-op once set. busy_timeout is per-connection state.
|
|
await using SqliteCommand command = connection.CreateCommand();
|
|
command.CommandText =
|
|
$"PRAGMA journal_mode=WAL; PRAGMA busy_timeout={(int)BusyTimeout.TotalMilliseconds};";
|
|
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
}
|