Files
MoShiro/backend/src/MoShiro.Backend.Storage.Sqlite/SqliteAuthStore.cs
T
justme 2e146d4da7
ci/woodpecker/push/main-amd64-build Pipeline was successful
refactor!: rename the project from Monitor to MoShiro [CI]
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
2026-10-07 02:03:25 +08:00

87 lines
5.1 KiB
C#

using Microsoft.Data.Sqlite;
using MoShiro.Backend.Application;
namespace MoShiro.Backend.Storage.Sqlite;
public sealed class SqliteAuthStore : IAuthStore
{
private const string UserColumns = "id, username, password_hash, password_salt, iterations, created_at";
private const string TokenColumns = "id, name, scope, token_sha256, created_at, last_used_at, revoked_at";
private readonly SqliteDatabase _db;
public SqliteAuthStore(SqliteDatabase db)
{
_db = db;
}
public ValueTask<int> CountUsersAsync(CancellationToken ct) =>
_db.ReadAsync(c => (int)c.Scalar<long>("SELECT COUNT(*) FROM users"), ct);
public ValueTask<UserRecord?> GetUserByNameAsync(string username, CancellationToken ct) =>
_db.ReadAsync(c => c.QueryOne($"SELECT {UserColumns} FROM users WHERE username = @u", ReadUser, ("@u", username)), ct);
public ValueTask<UserRecord?> GetUserAsync(string id, CancellationToken ct) =>
_db.ReadAsync(c => c.QueryOne($"SELECT {UserColumns} FROM users WHERE id = @id", ReadUser, ("@id", id)), ct);
public ValueTask<IReadOnlyList<UserRecord>> ListUsersAsync(CancellationToken ct) =>
_db.ReadAsync<IReadOnlyList<UserRecord>>(c => c.Query($"SELECT {UserColumns} FROM users ORDER BY created_at, id", ReadUser), ct);
public ValueTask<bool> CreateUserAsync(UserRecord user, CancellationToken ct) =>
_db.WriteAsync(
c => c.Exec(
"INSERT OR IGNORE INTO users (id, username, password_hash, password_salt, iterations, created_at) VALUES (@id, @u, @h, @s, @i, @at)",
("@id", user.Id), ("@u", user.Username), ("@h", user.PasswordHash), ("@s", user.PasswordSalt), ("@i", user.Iterations), ("@at", user.CreatedAtMs)) == 1,
ct);
public ValueTask UpdatePasswordAsync(string userId, byte[] hash, byte[] salt, int iterations, CancellationToken ct) =>
_db.ExecuteAsync(
c =>
{
c.Exec("UPDATE users SET password_hash = @h, password_salt = @s, iterations = @i WHERE id = @id", ("@id", userId), ("@h", hash), ("@s", salt), ("@i", iterations));
c.Exec("DELETE FROM sessions WHERE user_id = @id", ("@id", userId));
},
ct);
public ValueTask CreateSessionAsync(SessionRecord session, CancellationToken ct) =>
_db.ExecuteAsync(
c => c.Exec(
"INSERT INTO sessions (id_sha256, user_id, csrf_token, created_at, expires_at) VALUES (@id, @u, @csrf, @c, @e)",
("@id", session.IdSha256), ("@u", session.UserId), ("@csrf", session.CsrfToken), ("@c", session.CreatedAtMs), ("@e", session.ExpiresAtMs)),
ct);
public ValueTask<SessionRecord?> GetSessionAsync(byte[] idSha256, CancellationToken ct) =>
_db.ReadAsync(
c => c.QueryOne("SELECT id_sha256, user_id, csrf_token, created_at, expires_at FROM sessions WHERE id_sha256 = @id", r => new SessionRecord(r.Bytes(0), r.GetString(1), r.Bytes(2), r.GetInt64(3), r.GetInt64(4)), ("@id", idSha256)),
ct);
public ValueTask DeleteSessionAsync(byte[] idSha256, CancellationToken ct) =>
_db.ExecuteAsync(c => c.Exec("DELETE FROM sessions WHERE id_sha256 = @id", ("@id", idSha256)), ct);
public ValueTask DeleteExpiredSessionsAsync(long nowMs, CancellationToken ct) =>
_db.ExecuteAsync(c => c.Exec("DELETE FROM sessions WHERE expires_at <= @now", ("@now", nowMs)), ct);
public ValueTask CreateApiTokenAsync(ApiTokenRecord token, CancellationToken ct) =>
_db.ExecuteAsync(
c => c.Exec(
"INSERT INTO api_tokens (id, name, scope, token_sha256, created_at) VALUES (@id, @n, @s, @h, @at)",
("@id", token.Id), ("@n", token.Name), ("@s", token.Scope), ("@h", token.TokenSha256), ("@at", token.CreatedAtMs)),
ct);
public ValueTask<ApiTokenRecord?> FindApiTokenAsync(byte[] tokenSha256, CancellationToken ct) =>
_db.ReadAsync(c => c.QueryOne($"SELECT {TokenColumns} FROM api_tokens WHERE token_sha256 = @h", ReadToken, ("@h", tokenSha256)), ct);
public ValueTask<IReadOnlyList<ApiTokenRecord>> ListApiTokensAsync(CancellationToken ct) =>
_db.ReadAsync<IReadOnlyList<ApiTokenRecord>>(c => c.Query($"SELECT {TokenColumns} FROM api_tokens ORDER BY created_at DESC, id", ReadToken), ct);
public ValueTask<bool> RevokeApiTokenAsync(string id, long nowMs, CancellationToken ct) =>
_db.WriteAsync(c => c.Exec("UPDATE api_tokens SET revoked_at = @now WHERE id = @id AND revoked_at IS NULL", ("@id", id), ("@now", nowMs)) == 1, ct);
public ValueTask TouchApiTokenAsync(string id, long nowMs, CancellationToken ct) =>
_db.ExecuteAsync(c => c.Exec("UPDATE api_tokens SET last_used_at = @now WHERE id = @id", ("@id", id), ("@now", nowMs)), ct);
private static UserRecord ReadUser(SqliteDataReader r) => new(r.GetString(0), r.GetString(1), r.Bytes(2), r.Bytes(3), r.GetInt32(4), r.GetInt64(5));
private static ApiTokenRecord ReadToken(SqliteDataReader r) => new(r.GetString(0), r.GetString(1), r.GetString(2), r.Bytes(3), r.GetInt64(4), r.NullableLong(5), r.NullableLong(6));
}